You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by dc...@apache.org on 2022/04/23 03:17:56 UTC
[cassandra] branch trunk updated: Enhance SnakeYAML properties to be reusable outside of YAML parsing, support camel case conversion to snake case, and add support to ignore properties
This is an automated email from the ASF dual-hosted git repository.
dcapwell pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 3409e5d9b3 Enhance SnakeYAML properties to be reusable outside of YAML parsing, support camel case conversion to snake case, and add support to ignore properties
3409e5d9b3 is described below
commit 3409e5d9b341de2114d6410225922843539d4172
Author: David Capwell <dc...@apache.org>
AuthorDate: Fri Apr 22 18:35:26 2022 -0700
Enhance SnakeYAML properties to be reusable outside of YAML parsing, support camel case conversion to snake case, and add support to ignore properties
patch by David Capwell; reviewed by Caleb Rackliffe, Stefan Miklosovic for CASSANDRA-17166
---
.build/build-rat.xml | 3 +
CHANGES.txt | 1 +
build.xml | 5 +
.../config/CassandraRelevantProperties.java | 4 +
src/java/org/apache/cassandra/config/Config.java | 92 ++---
.../org/apache/cassandra/config/Converters.java | 77 ++--
.../org/apache/cassandra/config/DefaultLoader.java | 119 ++++++
.../apache/cassandra/config/EncryptionOptions.java | 45 ++-
.../cassandra/config/ForwardingProperty.java | 92 +++++
src/java/org/apache/cassandra/config/Loader.java | 37 ++
.../org/apache/cassandra/config/Properties.java | 196 ++++++++++
.../org/apache/cassandra/config/Replacement.java | 86 ++++
.../org/apache/cassandra/config/Replacements.java | 128 ++++++
.../cassandra/config/YamlConfigurationLoader.java | 295 +++++---------
.../apache/cassandra/db/virtual/SettingsTable.java | 220 ++++-------
.../org/apache/cassandra/tools/BulkLoader.java | 2 +-
.../org/apache/cassandra/tools/LoaderOptions.java | 2 +-
.../apache/cassandra/transport/SimpleClient.java | 2 +-
.../org/apache/cassandra/utils/FBUtilities.java | 20 +
test/data/config/version=3.0.0-alpha1.yml | 214 ++++++++++
test/data/config/version=3.11.0.yml | 247 ++++++++++++
test/data/config/version=4.0-alpha1.yml | 301 ++++++++++++++
.../upgrade/ConfigCompatabilityTestGenerate.java | 63 +++
.../cassandra/config/ConfigCompatabilityTest.java | 435 +++++++++++++++++++++
.../apache/cassandra/config/DefaultLoaderTest.java | 224 +++++++++++
.../org/apache/cassandra/config/LoaderTest.java | 59 +++
.../apache/cassandra/config/PropertiesTest.java | 100 +++++
.../apache/cassandra/config/SnakeYamlLoader.java | 90 +++++
.../config/YamlConfigurationLoaderTest.java | 54 +++
.../cassandra/db/virtual/SettingsTableTest.java | 74 ++--
.../apache/cassandra/net/MessagingServiceTest.java | 8 +-
.../apache/cassandra/utils/FBUtilitiesTest.java | 28 ++
.../cassandra/stress/util/JavaDriverClient.java | 2 +-
33 files changed, 2848 insertions(+), 477 deletions(-)
diff --git a/.build/build-rat.xml b/.build/build-rat.xml
index ba983a0e18..3c7736b1ef 100644
--- a/.build/build-rat.xml
+++ b/.build/build-rat.xml
@@ -69,6 +69,9 @@
<exclude name="**/test/data/jmxdump/cassandra-3.11-jmx.yaml"/>
<exclude name="**/test/data/jmxdump/cassandra-4.0-jmx.yaml"/>
<exclude name="**/test/data/jmxdump/cassandra-4.1-jmx.yaml"/>
+ <exclude name="**/test/data/config/version=3.0.0-alpha1.yml"/>
+ <exclude name="**/test/data/config/version=3.11.0.yml"/>
+ <exclude name="**/test/data/config/version=4.0-alpha1.yml"/>
<exclude name="**/test/resources/data/config/YamlConfigurationLoaderTest/shared_client_error_reporting_exclusions.yaml"/>
<exclude name="**/tools/cqlstress-counter-example.yaml"/>
<exclude name="**/tools/cqlstress-example.yaml"/>
diff --git a/CHANGES.txt b/CHANGES.txt
index 972f760442..9410f075bc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
4.1
+ * Enhance SnakeYAML properties to be reusable outside of YAML parsing, support camel case conversion to snake case, and add support to ignore properties (CASSANDRA-17166)
* nodetool compact should support using a key string to find the range to avoid operators having to manually do this (CASSANDRA-17537)
* Add guardrail for data disk usage (CASSANDRA-17150)
* Tool to list data paths of existing tables (CASSANDRA-17568)
diff --git a/build.xml b/build.xml
index 1c20100a5a..5bb53855f3 100644
--- a/build.xml
+++ b/build.xml
@@ -539,6 +539,9 @@
<dependency groupId="com.fasterxml.jackson.core" artifactId="jackson-databind" version="2.13.2"/>
<dependency groupId="com.fasterxml.jackson.core" artifactId="jackson-annotations" version="2.13.2"/>
<dependency groupId="com.fasterxml.jackson.datatype" artifactId="jackson-datatype-jsr310" version="2.13.2"/>
+ <dependency groupId="com.fasterxml.jackson.dataformat" artifactId="jackson-dataformat-yaml" version="2.13.2" scope="test">
+ <exclusion groupId="org.yaml" artifactId="snakeyaml"/>
+ </dependency>
<dependency groupId="com.googlecode.json-simple" artifactId="json-simple" version="1.1"/>
<dependency groupId="com.boundary" artifactId="high-scale-lib" version="1.0.6"/>
<dependency groupId="com.github.jbellis" artifactId="jamm" version="${jamm.version}"/>
@@ -765,6 +768,8 @@
<!-- coverage debs -->
<dependency groupId="org.jacoco" artifactId="org.jacoco.agent"/>
<dependency groupId="org.jacoco" artifactId="org.jacoco.ant"/>
+
+ <dependency groupId="com.fasterxml.jackson.dataformat" artifactId="jackson-dataformat-yaml"/>
</artifact:pom>
<!-- now the pom's for artifacts being deployed to Maven Central -->
diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
index 72da3307dc..5bfd5ee754 100644
--- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
+++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
@@ -246,6 +246,10 @@ public enum CassandraRelevantProperties
@Deprecated // should be removed in favor of flags in relevant startup check (FileSystemOwnershipCheck)
FILE_SYSTEM_CHECK_OWNERSHIP_TOKEN(FileSystemOwnershipCheck.FILE_SYSTEM_CHECK_OWNERSHIP_TOKEN),
+ // defaults to false for 4.1 but plan to switch to true in a later release
+ // the thinking is that environments may not work right off the bat so safer to add this feature disabled by default
+ CONFIG_ALLOW_SYSTEM_PROPERTIES("cassandra.config.allow_system_properties", "false"),
+
// properties for debugging simulator ASM output
TEST_SIMULATOR_PRINT_ASM("cassandra.test.simulator.print_asm", "none"),
TEST_SIMULATOR_PRINT_ASM_TYPES("cassandra.test.simulator.print_asm_types", ""),
diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java
index 7f6c926d08..e0961ed945 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -73,19 +73,19 @@ public class Config
public String authorizer;
public String role_manager;
public String network_authorizer;
- @Replaces(oldName = "permissions_validity_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "permissions_validity_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public volatile SmallestDurationMilliseconds permissions_validity = new SmallestDurationMilliseconds("2s");
public volatile int permissions_cache_max_entries = 1000;
@Replaces(oldName = "permissions_update_interval_in_ms", converter = Converters.MILLIS_CUSTOM_DURATION, deprecated = true)
public volatile SmallestDurationMilliseconds permissions_update_interval = null;
public volatile boolean permissions_cache_active_update = false;
- @Replaces(oldName = "roles_validity_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "roles_validity_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public volatile SmallestDurationMilliseconds roles_validity = new SmallestDurationMilliseconds("2s");
public volatile int roles_cache_max_entries = 1000;
@Replaces(oldName = "roles_update_interval_in_ms", converter = Converters.MILLIS_CUSTOM_DURATION, deprecated = true)
public volatile SmallestDurationMilliseconds roles_update_interval = null;
public volatile boolean roles_cache_active_update = false;
- @Replaces(oldName = "credentials_validity_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "credentials_validity_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public volatile SmallestDurationMilliseconds credentials_validity = new SmallestDurationMilliseconds("2s");
public volatile int credentials_cache_max_entries = 1000;
@Replaces(oldName = "credentials_update_interval_in_ms", converter = Converters.MILLIS_CUSTOM_DURATION, deprecated = true)
@@ -98,7 +98,7 @@ public class Config
public boolean auto_bootstrap = true;
public volatile boolean hinted_handoff_enabled = true;
public Set<String> hinted_handoff_disabled_datacenters = Sets.newConcurrentHashSet();
- @Replaces(oldName = "max_hint_window_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "max_hint_window_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public volatile SmallestDurationMilliseconds max_hint_window = new SmallestDurationMilliseconds("3h");
public String hints_directory;
public boolean hint_window_persistent_enabled = true;
@@ -121,28 +121,28 @@ public class Config
/** Triggers automatic allocation of tokens if set, based on the provided replica count for a datacenter */
public Integer allocate_tokens_for_local_replication_factor = null;
- @Replaces(oldName = "native_transport_idle_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "native_transport_idle_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public SmallestDurationMilliseconds native_transport_idle_timeout = new SmallestDurationMilliseconds("0ms");
- @Replaces(oldName = "request_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds request_timeout = new SmallestDurationMilliseconds("10000ms");
- @Replaces(oldName = "read_request_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "read_request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds read_request_timeout = new SmallestDurationMilliseconds("5000ms");
- @Replaces(oldName = "range_request_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "range_request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds range_request_timeout = new SmallestDurationMilliseconds("10000ms");
- @Replaces(oldName = "write_request_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "write_request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds write_request_timeout = new SmallestDurationMilliseconds("2000ms");
- @Replaces(oldName = "counter_write_request_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "counter_write_request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds counter_write_request_timeout = new SmallestDurationMilliseconds("5000ms");
- @Replaces(oldName = "cas_contention_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "cas_contention_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds cas_contention_timeout = new SmallestDurationMilliseconds("1800ms");
- @Replaces(oldName = "truncate_request_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "truncate_request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds truncate_request_timeout = new SmallestDurationMilliseconds("60000ms");
public volatile Long repair_request_timeout_in_ms = 120000L;
@@ -154,7 +154,7 @@ public class Config
@Replaces(oldName = "cross_node_timeout", converter = Converters.IDENTITY, deprecated = true)
public boolean internode_timeout = true;
- @Replaces(oldName = "slow_query_log_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "slow_query_log_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile SmallestDurationMilliseconds slow_query_log_timeout = new SmallestDurationMilliseconds("500ms");
public volatile double phi_convict_threshold = 8.0;
@@ -169,16 +169,16 @@ public class Config
public Integer concurrent_replicates = null;
public int memtable_flush_writers = 0;
- @Replaces(oldName = "memtable_heap_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "memtable_heap_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes memtable_heap_space;
- @Replaces(oldName = "memtable_offheap_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "memtable_offheap_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes memtable_offheap_space;
public Float memtable_cleanup_threshold = null;
// Limit the maximum depth of repair session merkle trees
@Deprecated
public volatile Integer repair_session_max_tree_depth = null;
- @Replaces(oldName = "repair_session_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "repair_session_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public volatile SmallestDataStorageMebibytes repair_session_space = null;
public volatile boolean use_offheap_merkle_trees = true;
@@ -234,24 +234,24 @@ public class Config
// Defensive settings for protecting Cassandra from true network partitions. See (CASSANDRA-14358) for details.
// The amount of time to wait for internode tcp connections to establish.
- @Replaces(oldName = "internode_tcp_connect_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "internode_tcp_connect_timeout_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public volatile SmallestDurationMilliseconds internode_tcp_connect_timeout = new SmallestDurationMilliseconds("2s");
// The amount of time unacknowledged data is allowed on a connection before we throw out the connection
// Note this is only supported on Linux + epoll, and it appears to behave oddly above a setting of 30000
// (it takes much longer than 30s) as of Linux 4.12. If you want something that high set this to 0
// (which picks up the OS default) and configure the net.ipv4.tcp_retries2 sysctl to be ~8.
- @Replaces(oldName = "internode_tcp_user_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "internode_tcp_user_timeout_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public volatile SmallestDurationMilliseconds internode_tcp_user_timeout = new SmallestDurationMilliseconds("30s");
// Similar to internode_tcp_user_timeout but used specifically for streaming connection.
// The default is 5 minutes. Increase it or set it to 0 in order to increase the timeout.
- @Replaces(oldName = "internode_streaming_tcp_user_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "internode_streaming_tcp_user_timeout_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public volatile SmallestDurationMilliseconds internode_streaming_tcp_user_timeout = new SmallestDurationMilliseconds("300s"); // 5 minutes
public boolean start_native_transport = true;
public int native_transport_port = 9042;
public Integer native_transport_port_ssl = null;
public int native_transport_max_threads = 128;
- @Replaces(oldName = "native_transport_max_frame_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "native_transport_max_frame_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes native_transport_max_frame_size = new SmallestDataStorageMebibytes("16MiB");
public volatile long native_transport_max_concurrent_connections = -1L;
public volatile long native_transport_max_concurrent_connections_per_ip = -1L;
@@ -276,7 +276,7 @@ public class Config
* Default is the same as the native protocol frame limit: 256MiB.
* See AbstractType for how it is used.
*/
- @Replaces(oldName = "max_value_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "max_value_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes max_value_size = new SmallestDataStorageMebibytes("256MiB");
public boolean snapshot_before_compaction = false;
@@ -297,9 +297,9 @@ public class Config
public volatile Integer concurrent_compactors;
@Replaces(oldName = "compaction_throughput_mb_per_sec", converter = Converters.MEBIBYTES_PER_SECOND_DATA_RATE, deprecated = true)
public volatile DataRateSpec compaction_throughput = new DataRateSpec("16MiB/s");
- @Replaces(oldName = "compaction_large_partition_warning_threshold_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "compaction_large_partition_warning_threshold_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public volatile SmallestDataStorageMebibytes compaction_large_partition_warning_threshold = new SmallestDataStorageMebibytes("100MiB");
- @Replaces(oldName = "min_free_space_per_drive_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "min_free_space_per_drive_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes min_free_space_per_drive = new SmallestDataStorageMebibytes("50MiB");
public volatile Integer compaction_tombstone_warning_threshold = 100000;
@@ -332,7 +332,7 @@ public class Config
// Commit Log
public String commitlog_directory;
- @Replaces(oldName = "commitlog_total_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "commitlog_total_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes commitlog_total_space;
public CommitLogSync commitlog_sync;
@@ -340,16 +340,16 @@ public class Config
* @deprecated since 4.0 This value was near useless, and we're not using it anymore
*/
public double commitlog_sync_batch_window_in_ms = Double.NaN;
- @Replaces(oldName = "commitlog_sync_group_window_in_ms", converter = Converters.MILLIS_DOUBLE_DURATION, deprecated = true)
+ @Replaces(oldName = "commitlog_sync_group_window_in_ms", converter = Converters.MILLIS_DURATION_DOUBLE, deprecated = true)
public SmallestDurationMilliseconds commitlog_sync_group_window = new SmallestDurationMilliseconds("0ms");
- @Replaces(oldName = "commitlog_sync_period_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "commitlog_sync_period_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds commitlog_sync_period = new SmallestDurationMilliseconds("0ms");
- @Replaces(oldName = "commitlog_segment_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "commitlog_segment_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes commitlog_segment_size = new SmallestDataStorageMebibytes("32MiB");
public ParameterizedClass commitlog_compression;
public FlushCompression flush_compression = FlushCompression.fast;
public int commitlog_max_compression_buffers_in_pool = 3;
- @Replaces(oldName = "periodic_commitlog_sync_lag_block_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "periodic_commitlog_sync_lag_block_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds periodic_commitlog_sync_lag_block;
public TransparentDataEncryptionOptions transparent_data_encryption_options = new TransparentDataEncryptionOptions();
@@ -362,9 +362,9 @@ public class Config
// When false, new CDC mutations can always be added. But it will remove the oldest CDC commit log segment on full.
public volatile boolean cdc_block_writes = true;
public String cdc_raw_directory;
- @Replaces(oldName = "cdc_total_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "cdc_total_space_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes cdc_total_space = new SmallestDataStorageMebibytes("0MiB");
- @Replaces(oldName = "cdc_free_space_check_interval_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "cdc_free_space_check_interval_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds cdc_free_space_check_interval = new SmallestDurationMilliseconds("250ms");
@Deprecated
@@ -372,9 +372,9 @@ public class Config
public String endpoint_snitch;
public boolean dynamic_snitch = true;
- @Replaces(oldName = "dynamic_snitch_update_interval_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "dynamic_snitch_update_interval_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds dynamic_snitch_update_interval = new SmallestDurationMilliseconds("100ms");
- @Replaces(oldName = "dynamic_snitch_reset_interval_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "dynamic_snitch_reset_interval_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds dynamic_snitch_reset_interval = new SmallestDurationMilliseconds("10m");
public double dynamic_snitch_badness_threshold = 1.0;
@@ -390,9 +390,9 @@ public class Config
@Replaces(oldName = "batchlog_replay_throttle_in_kb", converter = Converters.KIBIBYTES_DATASTORAGE, deprecated = true)
public SmallestDataStorageKibibytes batchlog_replay_throttle = new SmallestDataStorageKibibytes("1024KiB");
public int max_hints_delivery_threads = 2;
- @Replaces(oldName = "hints_flush_period_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "hints_flush_period_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds hints_flush_period = new SmallestDurationMilliseconds("10s");
- @Replaces(oldName = "max_hints_file_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "max_hints_file_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes max_hints_file_size = new SmallestDataStorageMebibytes("128MiB");
public volatile DataStorageSpec max_hints_size_per_host = new DataStorageSpec("0B"); // 0 means disabled
@@ -404,24 +404,24 @@ public class Config
@Replaces(oldName = "trickle_fsync_interval_in_kb", converter = Converters.KIBIBYTES_DATASTORAGE, deprecated = true)
public SmallestDataStorageKibibytes trickle_fsync_interval = new SmallestDataStorageKibibytes("10240KiB");
- @Replaces(oldName = "sstable_preemptive_open_interval_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "sstable_preemptive_open_interval_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public volatile SmallestDataStorageMebibytes sstable_preemptive_open_interval = new SmallestDataStorageMebibytes("50MiB");
public volatile boolean key_cache_migrate_during_compaction = true;
public volatile int key_cache_keys_to_save = Integer.MAX_VALUE;
- @Replaces(oldName = "key_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "key_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_LONG, deprecated = true)
public SmallestDataStorageMebibytes key_cache_size = null;
@Replaces(oldName = "key_cache_save_period", converter = Converters.SECONDS_CUSTOM_DURATION, deprecated = true)
public volatile SmallestDurationSeconds key_cache_save_period = new SmallestDurationSeconds("4h");
public String row_cache_class_name = "org.apache.cassandra.cache.OHCProvider";
- @Replaces(oldName = "row_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "row_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_LONG, deprecated = true)
public SmallestDataStorageMebibytes row_cache_size = new SmallestDataStorageMebibytes("0MiB");
@Replaces(oldName = "row_cache_save_period", converter = Converters.SECONDS_CUSTOM_DURATION, deprecated = true)
public volatile SmallestDurationSeconds row_cache_save_period = new SmallestDurationSeconds("0s");
public volatile int row_cache_keys_to_save = Integer.MAX_VALUE;
- @Replaces(oldName = "counter_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "counter_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_LONG, deprecated = true)
public SmallestDataStorageMebibytes counter_cache_size = null;
@Replaces(oldName = "counter_cache_save_period", converter = Converters.SECONDS_CUSTOM_DURATION, deprecated = true)
public volatile SmallestDurationSeconds counter_cache_save_period = new SmallestDurationSeconds("7200s");
@@ -435,10 +435,10 @@ public class Config
private static boolean isClientMode = false;
private static Supplier<Config> overrideLoadConfig = null;
- @Replaces(oldName = "networking_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "networking_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes networking_cache_size;
- @Replaces(oldName = "file_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "file_cache_size_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_INT, deprecated = true)
public SmallestDataStorageMebibytes file_cache_size;
public boolean file_cache_enabled = Boolean.getBoolean("cassandra.file_cache_enabled");
@@ -480,14 +480,14 @@ public class Config
public final ReplicaFilteringProtectionOptions replica_filtering_protection = new ReplicaFilteringProtectionOptions();
- @Replaces(oldName = "index_summary_capacity_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "index_summary_capacity_in_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_LONG, deprecated = true)
public volatile SmallestDataStorageMebibytes index_summary_capacity;
@Replaces(oldName = "index_summary_resize_interval_in_minutes", converter = Converters.MINUTES_DURATION, deprecated = true)
public volatile SmallestDurationMinutes index_summary_resize_interval = new SmallestDurationMinutes("60m");
- @Replaces(oldName = "gc_log_threshold_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "gc_log_threshold_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds gc_log_threshold = new SmallestDurationMilliseconds("200ms");
- @Replaces(oldName = "gc_warn_threshold_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "gc_warn_threshold_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
public SmallestDurationMilliseconds gc_warn_threshold = new SmallestDurationMilliseconds("1s");
// TTL for different types of trace events.
@@ -523,7 +523,7 @@ public class Config
* Size of the CQL prepared statements cache in MiB.
* Defaults to 1/256th of the heap size or 10MiB, whichever is greater.
*/
- @Replaces(oldName = "prepared_statements_cache_size_mb", converter = Converters.MEBIBYTES_DATA_STORAGE, deprecated = true)
+ @Replaces(oldName = "prepared_statements_cache_size_mb", converter = Converters.MEBIBYTES_DATA_STORAGE_LONG, deprecated = true)
public SmallestDataStorageMebibytes prepared_statements_cache_size = null;
@Replaces(oldName = "enable_user_defined_functions", converter = Converters.IDENTITY, deprecated = true)
@@ -575,14 +575,14 @@ public class Config
* Time in milliseconds after a warning will be emitted to the log and to the client that a UDF runs too long.
* (Only valid, if user_defined_functions_threads_enabled==true)
*/
- @Replaces(oldName = "user_defined_function_warn_timeout", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "user_defined_function_warn_timeout", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public SmallestDurationMilliseconds user_defined_functions_warn_timeout = new SmallestDurationMilliseconds("500ms");
/**
* Time in milliseconds after a fatal UDF run-time situation is detected and action according to
* user_function_timeout_policy will take place.
* (Only valid, if user_defined_functions_threads_enabled==true)
*/
- @Replaces(oldName = "user_defined_function_fail_timeout", converter = Converters.MILLIS_DURATION, deprecated = true)
+ @Replaces(oldName = "user_defined_function_fail_timeout", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public SmallestDurationMilliseconds user_defined_functions_fail_timeout = new SmallestDurationMilliseconds("1500ms");
/**
* Defines what to do when a UDF ran longer than user_defined_functions_fail_timeout.
diff --git a/src/java/org/apache/cassandra/config/Converters.java b/src/java/org/apache/cassandra/config/Converters.java
index ff072fee3c..5e5f15a699 100644
--- a/src/java/org/apache/cassandra/config/Converters.java
+++ b/src/java/org/apache/cassandra/config/Converters.java
@@ -36,26 +36,29 @@ public enum Converters
* This converter is used when we change the name of a cassandra.yaml configuration parameter but we want to be
* able to still use the old name too. No units involved.
*/
- IDENTITY(null, null, o -> o, o-> o),
- MILLIS_DURATION(Long.class, SmallestDurationMilliseconds.class,
- SmallestDurationMilliseconds::inMilliseconds,
- o -> o.toMilliseconds()),
- MILLIS_DOUBLE_DURATION(Double.class, SmallestDurationMilliseconds.class,
+ IDENTITY(null, null, o -> o, o -> o),
+ MILLIS_DURATION_LONG(Long.class, SmallestDurationMilliseconds.class,
+ SmallestDurationMilliseconds::inMilliseconds,
+ o -> o.toMilliseconds()),
+ MILLIS_DURATION_INT(Integer.class, SmallestDurationMilliseconds.class,
+ i -> SmallestDurationMilliseconds.inMilliseconds(i),
+ DurationSpec::toMillisecondsAsInt),
+ MILLIS_DURATION_DOUBLE(Double.class, SmallestDurationMilliseconds.class,
o -> Double.isNaN(o) ? SmallestDurationMilliseconds.inMilliseconds(0) : SmallestDurationMilliseconds.inDoubleMilliseconds(o),
o -> (double) o.toMilliseconds()),
/**
* This converter is used to support backward compatibility for parameters where in the past -1 was used as a value
* Example: credentials_update_interval_in_ms = -1 and credentials_update_interval = null are equal.
*/
- MILLIS_CUSTOM_DURATION(Long.class, SmallestDurationMilliseconds.class,
+ MILLIS_CUSTOM_DURATION(Integer.class, SmallestDurationMilliseconds.class,
o -> o == -1 ? null : SmallestDurationMilliseconds.inMilliseconds(o),
- o -> o == null ? -1 : o.toMilliseconds()),
- SECONDS_DURATION(Long.class, SmallestDurationSeconds.class,
- SmallestDurationSeconds::inSeconds,
- DurationSpec::toSeconds),
- NEGATIVE_SECONDS_DURATION(Long.class, SmallestDurationSeconds.class,
+ o -> o == null ? -1 : o.toMillisecondsAsInt()),
+ SECONDS_DURATION(Integer.class, SmallestDurationSeconds.class,
+ i -> SmallestDurationSeconds.inSeconds(i),
+ DurationSpec::toSecondsAsInt),
+ NEGATIVE_SECONDS_DURATION(Integer.class, SmallestDurationSeconds.class,
o -> o < 0 ? SmallestDurationSeconds.inSeconds(0) : SmallestDurationSeconds.inSeconds(o),
- DurationSpec::toSeconds),
+ DurationSpec::toSecondsAsInt),
/**
* This converter is used to support backward compatibility for Duration parameters where we added the opportunity
* for the users to add a unit in the parameters' values but we didn't change the names. (key_cache_save_period,
@@ -65,18 +68,21 @@ public enum Converters
SECONDS_CUSTOM_DURATION(String.class, SmallestDurationSeconds.class,
SmallestDurationSeconds::inSecondsString,
o -> Long.toString(o.toSeconds())),
- MINUTES_DURATION(Long.class, SmallestDurationMinutes.class,
- SmallestDurationMinutes::inMinutes,
- DurationSpec::toMinutes),
- MEBIBYTES_DATA_STORAGE(Long.class, SmallestDataStorageMebibytes.class,
- SmallestDataStorageMebibytes::inMebibytes,
- DataStorageSpec::toMebibytes),
- KIBIBYTES_DATASTORAGE(Long.class, SmallestDataStorageKibibytes.class,
- SmallestDataStorageKibibytes::inKibibytes,
- DataStorageSpec::toKibibytes),
- BYTES_DATASTORAGE(Long.class, DataStorageSpec.class,
- DataStorageSpec::inBytes,
- DataStorageSpec::toBytes),
+ MINUTES_DURATION(Integer.class, SmallestDurationMinutes.class,
+ i -> SmallestDurationMinutes.inMinutes(i),
+ DurationSpec::toMinutesAsInt),
+ MEBIBYTES_DATA_STORAGE_LONG(Long.class, SmallestDataStorageMebibytes.class,
+ SmallestDataStorageMebibytes::inMebibytes,
+ DataStorageSpec::toMebibytes),
+ MEBIBYTES_DATA_STORAGE_INT(Integer.class, SmallestDataStorageMebibytes.class,
+ i -> SmallestDataStorageMebibytes.inMebibytes(i),
+ DataStorageSpec::toMebibytesAsInt),
+ KIBIBYTES_DATASTORAGE(Integer.class, SmallestDataStorageKibibytes.class,
+ i -> SmallestDataStorageKibibytes.inKibibytes(i),
+ DataStorageSpec::toKibibytesAsInt),
+ BYTES_DATASTORAGE(Integer.class, DataStorageSpec.class,
+ i -> DataStorageSpec.inBytes(i),
+ DataStorageSpec::toBytesAsInt),
/**
* This converter is used to support backward compatibility for parameters where in the past negative number was used as a value
* Example: native_transport_max_concurrent_requests_in_bytes_per_ip = -1 and native_transport_max_request_data_in_flight_per_ip = null
@@ -85,16 +91,16 @@ public enum Converters
BYTES_CUSTOM_DATASTORAGE(Long.class, DataStorageSpec.class,
o -> o == -1 ? null : DataStorageSpec.inBytes(o),
DataStorageSpec::toBytes),
- MEBIBYTES_PER_SECOND_DATA_RATE(Long.class, DataRateSpec.class,
- DataRateSpec::inMebibytesPerSecond,
- o -> (long) o.toMebibytesPerSecondAsInt()),
+ MEBIBYTES_PER_SECOND_DATA_RATE(Integer.class, DataRateSpec.class,
+ i -> DataRateSpec.inMebibytesPerSecond(i),
+ DataRateSpec::toMebibytesPerSecondAsInt),
/**
* This converter is a custom one to support backward compatibility for stream_throughput_outbound and
* inter_dc_stream_throughput_outbound which were provided in megatibs per second prior CASSANDRA-15234.
*/
- MEGABITS_TO_MEBIBYTES_PER_SECOND_DATA_RATE(Long.class, DataRateSpec.class,
- DataRateSpec::megabitsPerSecondInMebibytesPerSecond,
- o -> (long) o.toMegabitsPerSecondAsInt());
+ MEGABITS_TO_MEBIBYTES_PER_SECOND_DATA_RATE(Integer.class, DataRateSpec.class,
+ i -> DataRateSpec.megabitsPerSecondInMebibytesPerSecond(i),
+ DataRateSpec::toMegabitsPerSecondAsInt);
private final Class<?> oldType;
private final Class<?> newType;
@@ -121,7 +127,8 @@ public enum Converters
}
/**
- * Expected return type from {@link #convert(Object)}, and input type to {@link #deconvert(Object)}
+ * Expected return type from {@link #convert(Object)}, and input type to {@link #unconvert(Object)}
+ *
* @return type that {@link #convert(Object)} is expected to return
*/
public Class<?> getNewType()
@@ -133,8 +140,7 @@ public enum Converters
* Apply the converter specified as part of the {@link Replaces} annotation in {@link Config}
*
* @param value we will use from cassandra.yaml to create a new {@link Config} parameter of type {@link DurationSpec},
- * {@link DataRateSpec} or {@link DataStorageSpec}
- *
+ * {@link DataRateSpec} or {@link DataStorageSpec}
* @return new object of type {@link DurationSpec}, {@link DataRateSpec} or {@link DataStorageSpec}
*/
public Object convert(Object value)
@@ -149,12 +155,11 @@ public enum Converters
* compatibility
*
* @param value we will use to calculate the output value
- *
* @return the numeric value
*/
- public Object deconvert(Object value)
+ public Object unconvert(Object value)
{
- if (value == null) return 0;
+ if (value == null) return null;
return reverseConvert.apply(value);
}
}
diff --git a/src/java/org/apache/cassandra/config/DefaultLoader.java b/src/java/org/apache/cassandra/config/DefaultLoader.java
new file mode 100644
index 0000000000..6767d8aa61
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/DefaultLoader.java
@@ -0,0 +1,119 @@
+/*
+ * 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.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.introspector.FieldProperty;
+import org.yaml.snakeyaml.introspector.MethodProperty;
+import org.yaml.snakeyaml.introspector.Property;
+
+import static org.apache.cassandra.utils.FBUtilities.camelToSnake;
+
+public class DefaultLoader implements Loader
+{
+ @Override
+ public Map<String, Property> getProperties(Class<?> root)
+ {
+ Map<String, Property> properties = new HashMap<>();
+ for (Class<?> c = root; c != null; c = c.getSuperclass())
+ {
+ for (Field f : c.getDeclaredFields())
+ {
+ String name = camelToSnake(f.getName());
+ int modifiers = f.getModifiers();
+ if (!Modifier.isStatic(modifiers)
+ && !f.isAnnotationPresent(JsonIgnore.class)
+ && !Modifier.isTransient(modifiers)
+ && Modifier.isPublic(modifiers)
+ && !properties.containsKey(name))
+ properties.put(name, new FieldProperty(f));
+ }
+ }
+ try
+ {
+ PropertyDescriptor[] descriptors = Introspector.getBeanInfo(root).getPropertyDescriptors();
+ if (descriptors != null)
+ {
+ for (PropertyDescriptor d : descriptors)
+ {
+ String name = camelToSnake(d.getName());
+ Method writeMethod = d.getWriteMethod();
+ // if the property can't be written to, then ignore it
+ if (writeMethod == null || writeMethod.isAnnotationPresent(JsonIgnore.class))
+ continue;
+ // if read method exists, override the field version in case get/set does validation
+ if (properties.containsKey(name) && (d.getReadMethod() == null || d.getReadMethod().isAnnotationPresent(JsonIgnore.class)))
+ continue;
+ d.setName(name);
+ properties.put(name, new MethodPropertyPlus(d));
+ }
+ }
+ }
+ catch (IntrospectionException e)
+ {
+ throw new RuntimeException(e);
+ }
+ return properties;
+ }
+
+ /**
+ * .get() acts differently than .set() and doesn't do a good job showing the cause of the failure, this
+ * class rewrites to make the errors easier to reason with.
+ */
+ private static class MethodPropertyPlus extends MethodProperty
+ {
+ private final Method readMethod;
+
+ public MethodPropertyPlus(PropertyDescriptor property)
+ {
+ super(property);
+ this.readMethod = property.getReadMethod();
+ }
+
+ @Override
+ public Object get(Object object)
+ {
+ if (!isReadable())
+ throw new YAMLException("No readable property '" + getName() + "' on class: " + object.getClass().getName());
+
+ try
+ {
+ return readMethod.invoke(object);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new YAMLException("Unable to find getter for property '" + getName() + "' on class " + object.getClass().getName(), e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new YAMLException("Failed calling getter for property '" + getName() + "' on class " + object.getClass().getName(), e.getCause());
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java
index 246d13ffe1..eb6724f96d 100644
--- a/src/java/org/apache/cassandra/config/EncryptionOptions.java
+++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java
@@ -35,6 +35,7 @@ import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import org.apache.cassandra.locator.IEndpointSnitch;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.security.AbstractSslContextFactory;
@@ -104,7 +105,7 @@ public class EncryptionOptions
* We will wait to initialize this until applyConfig() call to make sure we do it only when the caller is ready
* to use this option instance.
*/
- public ISslContextFactory sslContextFactoryInstance;
+ public transient ISslContextFactory sslContextFactoryInstance;
public enum ConfigKey
{
@@ -262,7 +263,8 @@ public class EncryptionOptions
}
}
- private void initializeSslContextFactory() {
+ private void initializeSslContextFactory()
+ {
Map<String,Object> sslContextFactoryParameters = new HashMap<>();
prepareSslContextFactoryParameterizedKeys(sslContextFactoryParameters);
@@ -295,7 +297,8 @@ public class EncryptionOptions
}
private void putSslContextFactoryParameter(Map<String,Object> existingParameters, ConfigKey configKey,
- Object value) {
+ Object value)
+ {
if (value != null) {
existingParameters.put(configKey.getKeyName(), value);
}
@@ -318,7 +321,8 @@ public class EncryptionOptions
*
* @return if the channel should be encrypted
*/
- public Boolean isEnabled() {
+ public Boolean getEnabled()
+ {
ensureConfigApplied();
return isEnabled;
}
@@ -329,7 +333,8 @@ public class EncryptionOptions
* is probably a bad idea.
* @param enabled value to set
*/
- public void setEnabled(Boolean enabled) {
+ public void setEnabled(Boolean enabled)
+ {
ensureConfigNotApplied();
this.enabled = enabled;
}
@@ -343,7 +348,7 @@ public class EncryptionOptions
* Return type is Boolean even though it can never be null so that snakeyaml can find it
* @return if the channel may be encrypted
*/
- public Boolean isOptional()
+ public Boolean getOptional()
{
ensureConfigApplied();
return isOptional;
@@ -355,7 +360,8 @@ public class EncryptionOptions
* is probably a bad idea.
* @param optional value to set
*/
- public void setOptional(boolean optional) {
+ public void setOptional(Boolean optional)
+ {
ensureConfigNotApplied();
this.optional = optional;
}
@@ -367,38 +373,45 @@ public class EncryptionOptions
* @param protocol value to set
*/
@VisibleForTesting
- public void setProtocol(String protocol) {
+ public void setProtocol(String protocol)
+ {
this.protocol = protocol;
}
+ public String getProtocol()
+ {
+ return protocol;
+ }
+
/**
* Sets accepted TLS protocols for this channel. Note that this should only be called by
* the configuration parser or tests. It is public only for that purpose, mutating protocol state
* is probably a bad idea. The function casing is required for snakeyaml to find this setter for the protected field.
* @param accepted_protocols value to set
*/
- public void setaccepted_protocols(List<String> accepted_protocols) {
+ public void setAcceptedProtocols(List<String> accepted_protocols)
+ {
this.accepted_protocols = accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols);
}
- public List<String> acceptedProtocols()
+ public List<String> getAcceptedProtocols()
{
- return sslContextFactoryInstance.getAcceptedProtocols();
+ return sslContextFactoryInstance == null ? null : sslContextFactoryInstance.getAcceptedProtocols();
}
public String[] acceptedProtocolsArray()
{
- List<String> ap = acceptedProtocols();
+ List<String> ap = getAcceptedProtocols();
return ap == null ? new String[0] : ap.toArray(new String[0]);
}
public TlsEncryptionPolicy tlsEncryptionPolicy()
{
- if (isOptional())
+ if (getOptional())
{
return TlsEncryptionPolicy.OPTIONAL;
}
- else if (isEnabled())
+ else if (getEnabled())
{
return TlsEncryptionPolicy.ENCRYPTED;
}
@@ -408,7 +421,8 @@ public class EncryptionOptions
}
}
- public EncryptionOptions withSslContextFactory(ParameterizedClass sslContextFactoryClass) {
+ public EncryptionOptions withSslContextFactory(ParameterizedClass sslContextFactoryClass)
+ {
return new EncryptionOptions(sslContextFactoryClass, keystore, keystore_password, truststore,
truststore_password, cipher_suites,protocol, accepted_protocols, algorithm,
store_type, require_client_auth, require_endpoint_verification,enabled,
@@ -683,6 +697,7 @@ public class EncryptionOptions
* values of "dc" and "all". This method returns the explicit, raw value of {@link #optional}
* as set by the user (if set at all).
*/
+ @JsonIgnore
public boolean isExplicitlyOptional()
{
return optional != null && optional;
diff --git a/src/java/org/apache/cassandra/config/ForwardingProperty.java b/src/java/org/apache/cassandra/config/ForwardingProperty.java
new file mode 100644
index 0000000000..5e7840e99c
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/ForwardingProperty.java
@@ -0,0 +1,92 @@
+/*
+ * 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.lang.annotation.Annotation;
+import java.util.List;
+
+import org.yaml.snakeyaml.introspector.Property;
+
+/**
+ * This class delegates all calls of {@link Property} to a {@link #delegate()}, used for cases where a small number of
+ * methods want to be overriden from the delegate.
+ *
+ * This class acts as a decorator to a {@link Property} and allows decorating any method.
+ */
+public class ForwardingProperty extends Property
+{
+ private final Property delegate;
+
+ public ForwardingProperty(String name, Property property)
+ {
+ this(name, property.getType(), property);
+ }
+
+ public ForwardingProperty(String name, Class<?> type, Property property)
+ {
+ super(name, type);
+ this.delegate = property;
+ }
+
+ protected Property delegate()
+ {
+ return delegate;
+ }
+
+ @Override
+ public boolean isWritable()
+ {
+ return delegate().isWritable();
+ }
+
+ @Override
+ public boolean isReadable()
+ {
+ return delegate().isReadable();
+ }
+
+ @Override
+ public Class<?>[] getActualTypeArguments()
+ {
+ return delegate().getActualTypeArguments();
+ }
+
+ @Override
+ public void set(Object o, Object o1) throws Exception
+ {
+ delegate().set(o, o1);
+ }
+
+ @Override
+ public Object get(Object o)
+ {
+ return delegate().get(o);
+ }
+
+ @Override
+ public List<Annotation> getAnnotations()
+ {
+ return delegate().getAnnotations();
+ }
+
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> aClass)
+ {
+ return delegate().getAnnotation(aClass);
+ }
+}
diff --git a/src/java/org/apache/cassandra/config/Loader.java b/src/java/org/apache/cassandra/config/Loader.java
new file mode 100644
index 0000000000..31676458cc
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/Loader.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Map;
+
+import org.yaml.snakeyaml.introspector.Property;
+
+public interface Loader
+{
+ Map<String, Property> getProperties(Class<?> root);
+
+ default Map<String, Property> flatten(Class<?> root)
+ {
+ return Properties.flatten(this, getProperties(root));
+ }
+
+ default Map<String, Property> flatten(Class<?> root, String delimiter)
+ {
+ return Properties.flatten(this, getProperties(root), delimiter);
+ }
+}
diff --git a/src/java/org/apache/cassandra/config/Properties.java b/src/java/org/apache/cassandra/config/Properties.java
new file mode 100644
index 0000000000..79852d4af6
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/Properties.java
@@ -0,0 +1,196 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Queue;
+
+import com.google.common.collect.Maps;
+
+import org.yaml.snakeyaml.introspector.Property;
+
+/**
+ * Utility class for working with {@link Property} types.
+ */
+public final class Properties
+{
+ public static final String DELIMITER = ".";
+
+ private Properties()
+ {
+ }
+
+ /**
+ * Given two properties (root, leaf), calls first go through root and passed to leaf.
+ *
+ * <pre>{@code leaf.get(root.get(o))}</pre>
+ *
+ * @param root first property in the chain
+ * @param leaf last property in the chain
+ * @param delimiter for joining names
+ * @return new Property which combines root -> leaf
+ */
+ public static Property andThen(Property root, Property leaf, String delimiter)
+ {
+ return new AndThen(root, leaf, delimiter);
+ }
+
+ /**
+ * Given two properties (root, leaf), calls first go through root and passed to leaf.
+ *
+ * <pre>{@code leaf.get(root.get(o))}</pre>
+ *
+ * @param root first property in the chain
+ * @param leaf last property in the chain
+ * @return new Property which combines root -> leaf
+ */
+ public static Property andThen(Property root, Property leaf)
+ {
+ return andThen(root, leaf, DELIMITER);
+ }
+
+ /**
+ * Given a map of Properties, takes any "nested" property (non primitive, value-type, or collection), and
+ * expands them, producing 1 or more Properties.
+ *
+ * @param loader for mapping type to map of properties
+ * @param input map to flatten
+ * @return map of all flattened properties
+ */
+ public static Map<String, Property> flatten(Loader loader, Map<String, Property> input)
+ {
+ return flatten(loader, input, DELIMITER);
+ }
+
+ /**
+ * Given a map of Properties, takes any "nested" property (non primitive, value-type, or collection), and
+ * expands them, producing 1 or more Properties.
+ *
+ * @param loader for mapping type to map of properties
+ * @param input map to flatten
+ * @param delimiter for joining names
+ * @return map of all flattened properties
+ */
+ public static Map<String, Property> flatten(Loader loader, Map<String, Property> input, String delimiter)
+ {
+ Queue<Property> queue = new ArrayDeque<>(input.values());
+
+ Map<String, Property> output = Maps.newHashMapWithExpectedSize(input.size());
+ while (!queue.isEmpty())
+ {
+ Property prop = queue.poll();
+ Map<String, Property> children = isPrimitive(prop) || isCollection(prop) ? Collections.emptyMap() : loader.getProperties(prop.getType());
+ if (children.isEmpty())
+ {
+ // not nested, so assume properties can be handled
+ output.put(prop.getName(), prop);
+ }
+ else
+ {
+ children.values().stream().map(p -> andThen(prop, p, delimiter)).forEach(queue::add);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * @return true if proeprty type is a collection
+ */
+ public static boolean isCollection(Property prop)
+ {
+ return Collection.class.isAssignableFrom(prop.getType()) || Map.class.isAssignableFrom(prop.getType());
+ }
+
+ /**
+ * @return true if property type is a primitive, or well known value type (may return false for user defined value types)
+ */
+ public static boolean isPrimitive(Property prop)
+ {
+ Class<?> type = prop.getType();
+ return type.isPrimitive() || type.isEnum() || type.equals(String.class) || Number.class.isAssignableFrom(type) || Boolean.class.equals(type);
+ }
+
+ /**
+ * @return default implementation of {@link Loader}
+ */
+ public static Loader defaultLoader()
+ {
+ return new DefaultLoader();
+ }
+
+ /**
+ * @return a new property with an updated name
+ */
+ public static Property rename(String newName, Property prop)
+ {
+ return new ForwardingProperty(newName, prop);
+ }
+
+ private static final class AndThen extends ForwardingProperty
+ {
+ private final Property root;
+ private final Property leaf;
+
+ AndThen(Property root, Property leaf, String delimiter)
+ {
+ super(root.getName() + delimiter + leaf.getName(), leaf);
+ this.root = root;
+ this.leaf = leaf;
+ }
+
+ @Override
+ public void set(Object object, Object value) throws Exception
+ {
+ Object parent = root.get(object);
+ if (parent == null)
+ {
+ // see: org.yaml.snakeyaml.constructor.BaseConstructor.newInstance(java.lang.Class<?>, org.yaml.snakeyaml.nodes.Node, boolean)
+ // That method is what creates the types, and it boils down to this call. There is a TypeDescription
+ // class that we don't use, so boils down to "null" in our existing logic... if we start using TypeDescription
+ // to build the object, then we will need to rewrite this logic to work with BaseConstructor.
+ Constructor<?> c = root.getType().getDeclaredConstructor();
+ c.setAccessible(true);
+ parent = c.newInstance();
+ root.set(object, parent);
+ }
+ leaf.set(parent, value);
+ }
+
+ @Override
+ public Object get(Object object)
+ {
+ try
+ {
+ Object parent = root.get(object);
+ if (parent == null)
+ return null;
+ return leaf.get(parent);
+ }
+ catch (Exception e)
+ {
+ if (!(root instanceof AndThen))
+ e.addSuppressed(new RuntimeException("Error calling get() on " + this));
+ throw e;
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/config/Replacement.java b/src/java/org/apache/cassandra/config/Replacement.java
new file mode 100644
index 0000000000..b3fb330143
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/Replacement.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Objects;
+
+import org.yaml.snakeyaml.introspector.Property;
+
+/**
+ * Holder for replacements to support backward compatibility between old and new names and types
+ * of configuration parameters (CASSANDRA-15234)
+ */
+public final class Replacement
+{
+ /**
+ * Currently we use Config class
+ */
+ public final Class<?> parent;
+ /**
+ * Old name of the configuration parameter
+ */
+ public final String oldName;
+ /**
+ * Old type of the configuration parameter
+ */
+ public final Class<?> oldType;
+ /**
+ * New name used for the configuration parameter
+ */
+ public final String newName;
+ /**
+ * Converter to be used according to the old default unit which was provided as a suffix of the configuration
+ * parameter
+ */
+ public final Converters converter;
+ public final boolean deprecated;
+
+ public Replacement(Class<?> parent,
+ String oldName,
+ Class<?> oldType,
+ String newName,
+ Converters converter,
+ boolean deprecated)
+ {
+ this.parent = Objects.requireNonNull(parent);
+ this.oldName = Objects.requireNonNull(oldName);
+ this.oldType = Objects.requireNonNull(oldType);
+ this.newName = Objects.requireNonNull(newName);
+ this.converter = Objects.requireNonNull(converter);
+ // by default deprecated is false
+ this.deprecated = deprecated;
+ }
+
+ public Property toProperty(Property newProperty)
+ {
+ return new ForwardingProperty(oldName, oldType, newProperty)
+ {
+ @Override
+ public void set(Object o, Object o1) throws Exception
+ {
+ newProperty.set(o, converter.convert(o1));
+ }
+
+ @Override
+ public Object get(Object o)
+ {
+ return converter.unconvert(newProperty.get(o));
+ }
+ };
+ }
+}
diff --git a/src/java/org/apache/cassandra/config/Replacements.java b/src/java/org/apache/cassandra/config/Replacements.java
new file mode 100644
index 0000000000..f6577cb0a0
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/Replacements.java
@@ -0,0 +1,128 @@
+/*
+ * 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.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+
+public final class Replacements
+{
+ private Replacements()
+ {
+ }
+
+ /**
+ * @param klass to get replacements for
+ * @return map of old names and replacements needed.
+ */
+ public static Map<Class<? extends Object>, Map<String, Replacement>> getNameReplacements(Class<? extends Object> klass)
+ {
+ List<Replacement> replacements = getReplacementsRecursive(klass);
+ Map<Class<?>, Map<String, Replacement>> objectOldNames = new HashMap<>();
+ for (Replacement r : replacements)
+ {
+ Map<String, Replacement> oldNames = objectOldNames.computeIfAbsent(r.parent, ignore -> new HashMap<>());
+ if (!oldNames.containsKey(r.oldName))
+ oldNames.put(r.oldName, r);
+ else
+ {
+ throw new ConfigurationException("Invalid annotations, you have more than one @Replaces annotation in " +
+ "Config class with same old name(" + r.oldName + ") defined.");
+ }
+ }
+ return objectOldNames;
+ }
+
+ /**
+ * @param klass to get replacements for
+ * @return map of old names and replacements needed.
+ */
+ private static List<Replacement> getReplacementsRecursive(Class<?> klass)
+ {
+ Set<Class<?>> seen = new HashSet<>(); // to make sure not to process the same type twice
+ List<Replacement> accum = new ArrayList<>();
+ getReplacementsRecursive(seen, accum, klass);
+ return accum.isEmpty() ? Collections.emptyList() : accum;
+ }
+
+ private static void getReplacementsRecursive(Set<Class<?>> seen,
+ List<Replacement> accum,
+ Class<?> klass)
+ {
+ accum.addAll(getReplacements(klass));
+ for (Field field : klass.getDeclaredFields())
+ {
+ if (seen.add(field.getType()))
+ {
+ // first time looking at this type, walk it
+ getReplacementsRecursive(seen, accum, field.getType());
+ }
+ }
+ }
+
+ private static List<Replacement> getReplacements(Class<?> klass)
+ {
+ List<Replacement> replacements = new ArrayList<>();
+ for (Field field : klass.getDeclaredFields())
+ {
+ String newName = field.getName();
+ Class<?> newType = field.getType();
+ final ReplacesList[] byType = field.getAnnotationsByType(ReplacesList.class);
+ if (byType == null || byType.length == 0)
+ {
+ Replaces r = field.getAnnotation(Replaces.class);
+ if (r != null)
+ addReplacement(klass, replacements, newName, newType, r);
+ }
+ else
+ {
+ for (ReplacesList replacesList : byType)
+ for (Replaces r : replacesList.value())
+ addReplacement(klass, replacements, newName, newType, r);
+ }
+ }
+ return replacements.isEmpty() ? Collections.emptyList() : replacements;
+ }
+
+ private static void addReplacement(Class<?> klass,
+ List<Replacement> replacements,
+ String newName, Class<?> newType,
+ Replaces r)
+ {
+ String oldName = r.oldName();
+
+ boolean deprecated = r.deprecated();
+
+ Class<?> oldType = r.converter().getOldType();
+ if (oldType == null)
+ oldType = newType;
+ Class<?> expectedNewType = r.converter().getNewType();
+ if (expectedNewType != null)
+ assert expectedNewType.equals(newType) : String.format("Converter is expected to return %s but %s#%s expects %s", expectedNewType, klass, newName, newType);
+
+ replacements.add(new Replacement(klass, oldName, oldType, newName, r.converter(), deprecated));
+ }
+}
diff --git a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
index 92039fc8e7..1b4520754f 100644
--- a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
+++ b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
@@ -20,13 +20,10 @@ package org.apache.cassandra.config;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -41,13 +38,11 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
-
-import org.apache.cassandra.io.util.File;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.io.util.File;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.composer.Composer;
@@ -59,11 +54,18 @@ import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.Node;
+import static org.apache.cassandra.config.Replacements.getNameReplacements;
+
public class YamlConfigurationLoader implements ConfigurationLoader
{
private static final Logger logger = LoggerFactory.getLogger(YamlConfigurationLoader.class);
private final static String DEFAULT_CONFIGURATION = "cassandra.yaml";
+ /**
+ * This is related to {@link Config#PROPERTY_PREFIX} but is different to make sure Config properties updated via
+ * system properties do not conflict with other system properties; the name "settings" matches system_views.settings.
+ */
+ static final String SYSTEM_PROPERTY_PREFIX = "cassandra.settings.";
/**
* Inspect the classpath to find storage configuration file
@@ -138,6 +140,7 @@ public class YamlConfigurationLoader implements ConfigurationLoader
Yaml yaml = new Yaml(constructor);
Config result = loadConfig(yaml, configBytes);
propertiesChecker.check();
+ maybeAddSystemProperties(result);
return result;
}
catch (YAMLException e)
@@ -146,6 +149,26 @@ public class YamlConfigurationLoader implements ConfigurationLoader
}
}
+ private static void maybeAddSystemProperties(Object obj)
+ {
+ if (CassandraRelevantProperties.CONFIG_ALLOW_SYSTEM_PROPERTIES.getBoolean())
+ {
+ java.util.Properties props = System.getProperties();
+ Map<String, String> map = new HashMap<>();
+ for (String name : props.stringPropertyNames())
+ {
+ if (name.startsWith(SYSTEM_PROPERTY_PREFIX))
+ {
+ String value = props.getProperty(name);
+ if (value != null)
+ map.put(name.replace(SYSTEM_PROPERTY_PREFIX, ""), value);
+ }
+ }
+ if (!map.isEmpty())
+ updateFromMap(map, false, obj);
+ }
+ }
+
private static String readStorageConfig(URL url)
{
String content;
@@ -210,6 +233,39 @@ public class YamlConfigurationLoader implements ConfigurationLoader
}
});
T value = (T) constructor.getSingleData(klass);
+ if (shouldCheck)
+ propertiesChecker.check();
+ maybeAddSystemProperties(value);
+ return value;
+ }
+
+ public static <T> T updateFromMap(Map<String, ? extends Object> map, boolean shouldCheck, T obj)
+ {
+ Class<T> klass = (Class<T>) obj.getClass();
+ Constructor constructor = new YamlConfigurationLoader.CustomConstructor(klass, klass.getClassLoader())
+ {
+ @Override
+ protected Object newInstance(Node node)
+ {
+ if (node.getType() == obj.getClass())
+ return obj;
+ return super.newInstance(node);
+ }
+ };
+ Map<Class<?>, Map<String, Replacement>> replacements = getNameReplacements(Config.class);
+ YamlConfigurationLoader.PropertiesChecker propertiesChecker = new YamlConfigurationLoader.PropertiesChecker(replacements);
+ constructor.setPropertyUtils(propertiesChecker);
+ Yaml yaml = new Yaml(constructor);
+ Node node = yaml.represent(map);
+ constructor.setComposer(new Composer(null, null)
+ {
+ @Override
+ public Node getSingleNode()
+ {
+ return node;
+ }
+ });
+ T value = (T) constructor.getSingleData(klass);
if (shouldCheck)
propertiesChecker.check();
return value;
@@ -261,6 +317,7 @@ public class YamlConfigurationLoader implements ConfigurationLoader
@VisibleForTesting
private static class PropertiesChecker extends PropertyUtils
{
+ private final Loader loader = Properties.defaultLoader();
private final Set<String> missingProperties = new HashSet<>();
private final Set<String> nullProperties = new HashSet<>();
@@ -283,92 +340,72 @@ public class YamlConfigurationLoader implements ConfigurationLoader
if (typeReplacements.containsKey(name))
{
Replacement replacement = typeReplacements.get(name);
-
- final Property newProperty = super.getProperty(type, replacement.newName);
- result = new Property(replacement.oldName, replacement.oldType)
- {
- @Override
- public Class<?>[] getActualTypeArguments()
- {
- return newProperty.getActualTypeArguments();
- }
-
- @Override
- public void set(Object o, Object o1) throws Exception
- {
- Object migratedValue = replacement.converter.convert(o1);
- newProperty.set(o, migratedValue);
- }
-
- @Override
- public Object get(Object o)
- {
- return newProperty.get(o);
- }
-
- @Override
- public List<Annotation> getAnnotations()
- {
- return null;
- }
-
- @Override
- public <A extends Annotation> A getAnnotation(Class<A> aClass)
- {
- return null;
- }
- };
+ result = replacement.toProperty(getProperty0(type, replacement.newName));
if (replacement.deprecated)
deprecationWarnings.add(replacement.oldName);
}
else
{
- result = super.getProperty(type, name);
+ result = getProperty0(type, name);
}
if (result instanceof MissingProperty)
{
missingProperties.add(result.getName());
}
+ else if (result.getAnnotation(Deprecated.class) != null)
+ {
+ deprecationWarnings.add(result.getName());
+ }
- return new Property(result.getName(), result.getType())
+ return new ForwardingProperty(result.getName(), result)
{
@Override
public void set(Object object, Object value) throws Exception
{
if (value == null && get(object) != null)
- {
nullProperties.add(getName());
- }
result.set(object, value);
}
- @Override
- public Class<?>[] getActualTypeArguments()
- {
- return result.getActualTypeArguments();
- }
-
@Override
public Object get(Object object)
{
return result.get(object);
}
+ };
+ }
- @Override
- public List<Annotation> getAnnotations()
- {
- return Collections.EMPTY_LIST;
- }
+ private Property getProperty0(Class<? extends Object> type, String name)
+ {
+ if (name.contains("."))
+ return getNestedProperty(type, name);
+ return getFlatProperty(type, name);
+ }
- @Override
- public <A extends Annotation> A getAnnotation(Class<A> aClass)
+ private Property getFlatProperty(Class<?> type, String name)
+ {
+ Property prop = loader.getProperties(type).get(name);
+ return prop == null ? new MissingProperty(name) : prop;
+ }
+
+ private Property getNestedProperty(Class<?> type, String name)
+ {
+ Property root = null;
+ for (String s : name.split("\\."))
+ {
+ Property prop = getFlatProperty(type, s);
+ if (prop instanceof MissingProperty)
{
- return null;
+ root = null;
+ break;
}
- };
+ root = root == null ? prop : Properties.andThen(root, prop);
+ type = root.getType();
+ }
+ return root != null ? root : new MissingProperty(name);
}
public void check() throws ConfigurationException
@@ -384,142 +421,6 @@ public class YamlConfigurationLoader implements ConfigurationLoader
}
}
- /**
- * @param klass to get replacements for
- * @return map of old names and replacements needed.
- */
- private static Map<Class<? extends Object>, Map<String, Replacement>> getNameReplacements(Class<? extends Object> klass)
- {
- List<Replacement> replacements = getReplacementsRecursive(klass);
- Map<Class<?>, Map<String, Replacement>> objectOldNames = new HashMap<>();
- for (Replacement r : replacements)
- {
- Map<String, Replacement> oldNames = objectOldNames.computeIfAbsent(r.parent, ignore -> new HashMap<>());
- if (!oldNames.containsKey(r.oldName))
- oldNames.put(r.oldName, r);
- else
- {
- throw new ConfigurationException("Invalid annotations, you have more than one @Replaces annotation in " +
- "Config class with same old name(" + r.oldName + ") defined.");
- }
- }
- return objectOldNames;
- }
-
- /**
- * @param klass to get replacements for
- * @return map of old names and replacements needed.
- */
- private static List<Replacement> getReplacementsRecursive(Class<?> klass)
- {
- Set<Class<?>> seen = new HashSet<>(); // to make sure not to process the same type twice
- List<Replacement> accum = new ArrayList<>();
- getReplacementsRecursive(seen, accum, klass);
- return accum.isEmpty() ? Collections.emptyList() : accum;
- }
-
- private static void getReplacementsRecursive(Set<Class<?>> seen,
- List<Replacement> accum,
- Class<?> klass)
- {
- accum.addAll(getReplacements(klass));
- for (Field field : klass.getDeclaredFields())
- {
- if (seen.add(field.getType()))
- {
- // first time looking at this type, walk it
- getReplacementsRecursive(seen, accum, field.getType());
- }
- }
- }
-
- private static List<Replacement> getReplacements(Class<?> klass)
- {
- List<Replacement> replacements = new ArrayList<>();
- for (Field field : klass.getDeclaredFields())
- {
- String newName = field.getName();
- Class<?> newType = field.getType();
- final ReplacesList[] byType = field.getAnnotationsByType(ReplacesList.class);
- if (byType == null || byType.length == 0)
- {
- Replaces r = field.getAnnotation(Replaces.class);
- if (r != null)
- addReplacement(klass, replacements, newName, newType, r);
- }
- else
- {
- for (ReplacesList replacesList : byType)
- for (Replaces r : replacesList.value())
- addReplacement(klass, replacements, newName, newType, r);
- }
- }
- return replacements.isEmpty() ? Collections.emptyList() : replacements;
- }
-
- private static void addReplacement(Class<?> klass,
- List<Replacement> replacements,
- String newName, Class<?> newType,
- Replaces r)
- {
- String oldName = r.oldName();
-
- boolean deprecated = r.deprecated();
-
- Class<?> oldType = r.converter().getOldType();
- if (oldType == null)
- oldType = newType;
- Class<?> expectedNewType = r.converter().getNewType();
- if (expectedNewType != null)
- assert expectedNewType.equals(newType) : String.format("Converter is expected to return %s but %s#%s expects %s", expectedNewType, klass, newName, newType);
-
- replacements.add(new Replacement(klass, oldName, oldType, newName, r.converter(), deprecated));
- }
- /**
- * Holder for replacements to support backward compatibility between old and new names and types
- * of configuration parameters (CASSANDRA-15234)
- */
- static final class Replacement
- {
- /**
- * Currently we use Config class
- */
- final Class<?> parent;
- /**
- * Old name of the configuration parameter
- */
- final String oldName;
- /**
- * Old type of the configuration parameter
- */
- final Class<?> oldType;
- /**
- * New name used for the configuration parameter
- */
- final String newName;
- /**
- * Converter to be used according to the old default unit which was provided as a suffix of the configuration
- * parameter
- */
- final Converters converter;
- final boolean deprecated;
-
- Replacement(Class<?> parent,
- String oldName,
- Class<?> oldType,
- String newName,
- Converters converter,
- boolean deprecated)
- {
- this.parent = Objects.requireNonNull(parent);
- this.oldName = Objects.requireNonNull(oldName);
- this.oldType = Objects.requireNonNull(oldType);
- this.newName = Objects.requireNonNull(newName);
- this.converter = Objects.requireNonNull(converter);
- // by default deprecated is false
- this.deprecated = deprecated;
- }
- }
}
diff --git a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
index ea427548a8..82cf2897fc 100644
--- a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
+++ b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
@@ -17,66 +17,32 @@
*/
package org.apache.cassandra.db.virtual;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
-import java.util.function.BiConsumer;
-import java.util.stream.Collectors;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Functions;
-import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
-import org.apache.cassandra.audit.AuditLogOptions;
-import org.apache.cassandra.config.*;
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.Loader;
+import org.apache.cassandra.config.Properties;
+import org.apache.cassandra.config.Replacement;
+import org.apache.cassandra.config.Replacements;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.transport.ServerError;
+import org.apache.cassandra.service.ClientWarn;
+import org.yaml.snakeyaml.introspector.Property;
final class SettingsTable extends AbstractVirtualTable
{
private static final String NAME = "name";
private static final String VALUE = "value";
- @VisibleForTesting
- static final Map<String, Field> FIELDS =
- Arrays.stream(Config.class.getFields())
- .filter(f -> !Modifier.isStatic(f.getModifiers()))
- .collect(Collectors.toMap(Field::getName, Functions.identity()));
-
- @VisibleForTesting
- static final Map<String, Field> ANNOTATED_FIELDS =
- Arrays.stream(Config.class.getFields())
- .filter(f -> !Modifier.isStatic(f.getModifiers()))
- .filter(f -> f.isAnnotationPresent(Replaces.class))
- .collect(Collectors.toMap(Field::getName, Functions.identity()));
-
- // CASSANDRA-15234 - a few configuration parameters kept their names but added unit to their value, only the
- // new value format is displayed for them
- private static final List<String> EXCLUDED_CONFIG = new ArrayList<String>()
- {
- {
- add("key_cache_save_period");
- add("row_cache_save_period");
- add("counter_cache_save_period");
- }
- };
-
- @VisibleForTesting
- final Map<String, BiConsumer<SimpleDataSet, Field>> overrides =
- ImmutableMap.<String, BiConsumer<SimpleDataSet, Field>>builder()
- .put("audit_logging_options", this::addAuditLoggingOptions)
- .put("client_encryption_options", this::addEncryptionOptions)
- .put("server_encryption_options", this::addEncryptionOptions)
- .put("transparent_data_encryption_options", this::addTransparentEncryptionOptions)
- .build();
+ private static final Map<String, String> BACKWARDS_COMPATABLE_NAMES = ImmutableMap.copyOf(getBackwardsCompatableNames());
+ protected static final Map<String, Property> PROPERTIES = ImmutableMap.copyOf(getProperties());
private final Config config;
@@ -97,66 +63,15 @@ final class SettingsTable extends AbstractVirtualTable
this.config = config;
}
- @VisibleForTesting
- Object getValue(Field f)
- {
- Object value;
- try
- {
- value = f.get(config);
- }
- catch (IllegalAccessException | IllegalArgumentException e)
- {
- throw new ServerError(e);
- }
- return value;
- }
-
- private void addValue(SimpleDataSet result, Field f)
- {
- Object value = getValue(f);
- if (value == null)
- {
- result.row(f.getName());
- }
- else if (overrides.containsKey(f.getName()))
- {
- overrides.get(f.getName()).accept(result, f);
- }
- else
- {
- if (value.getClass().isArray())
- value = Arrays.toString((Object[]) value);
- result.row(f.getName()).column(VALUE, value.toString());
-
- if (ANNOTATED_FIELDS.containsKey(f.getName()) && !EXCLUDED_CONFIG.contains(f.getName()))
- {
- Replaces annotation = f.getAnnotation(Replaces.class);
- result.row(annotation.oldName())
- .column(VALUE, annotation.converter()
- .deconvert(value)
- .toString());
- }
- }
- }
-
@Override
public DataSet data(DecoratedKey partitionKey)
{
SimpleDataSet result = new SimpleDataSet(metadata());
String name = UTF8Type.instance.compose(partitionKey.getKey());
- Field field = FIELDS.get(name);
- if (field != null)
- {
- addValue(result, field);
- }
- else
- {
- // rows created by overrides might be directly queried so include them in result to be possibly filtered
- for (String override : overrides.keySet())
- if (name.startsWith(override))
- addValue(result, FIELDS.get(override));
- }
+ if (BACKWARDS_COMPATABLE_NAMES.containsKey(name))
+ ClientWarn.instance.warn("key '" + name + "' is deprecated; should switch to '" + BACKWARDS_COMPATABLE_NAMES.get(name) + "'");
+ if (PROPERTIES.containsKey(name))
+ result.row(name).column(VALUE, getValue(PROPERTIES.get(name)));
return result;
}
@@ -164,56 +79,83 @@ final class SettingsTable extends AbstractVirtualTable
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
- for (Field setting : FIELDS.values())
- addValue(result, setting);
+ for (Map.Entry<String, Property> e : PROPERTIES.entrySet())
+ result.row(e.getKey()).column(VALUE, getValue(e.getValue()));
return result;
}
- private void addAuditLoggingOptions(SimpleDataSet result, Field f)
+ private String getValue(Property prop)
{
- Preconditions.checkArgument(AuditLogOptions.class.isAssignableFrom(f.getType()));
-
- AuditLogOptions value = (AuditLogOptions) getValue(f);
- result.row(f.getName() + "_enabled").column(VALUE, Boolean.toString(value.enabled));
- result.row(f.getName() + "_logger").column(VALUE, value.logger.class_name);
- result.row(f.getName() + "_audit_logs_dir").column(VALUE, value.audit_logs_dir);
- result.row(f.getName() + "_included_keyspaces").column(VALUE, value.included_keyspaces);
- result.row(f.getName() + "_excluded_keyspaces").column(VALUE, value.excluded_keyspaces);
- result.row(f.getName() + "_included_categories").column(VALUE, value.included_categories);
- result.row(f.getName() + "_excluded_categories").column(VALUE, value.excluded_categories);
- result.row(f.getName() + "_included_users").column(VALUE, value.included_users);
- result.row(f.getName() + "_excluded_users").column(VALUE, value.excluded_users);
+ Object value = prop.get(config);
+ return value == null ? null : value.toString();
}
- private void addEncryptionOptions(SimpleDataSet result, Field f)
+ private static Map<String, Property> getProperties()
{
- Preconditions.checkArgument(EncryptionOptions.class.isAssignableFrom(f.getType()));
-
- EncryptionOptions value = (EncryptionOptions) getValue(f);
- result.row(f.getName() + "_enabled").column(VALUE, Boolean.toString(value.isEnabled()));
- result.row(f.getName() + "_algorithm").column(VALUE, value.algorithm);
- result.row(f.getName() + "_protocol").column(VALUE, Objects.toString(value.acceptedProtocols(), null));
- result.row(f.getName() + "_cipher_suites").column(VALUE, Objects.toString(value.cipher_suites, null));
- result.row(f.getName() + "_client_auth").column(VALUE, Boolean.toString(value.require_client_auth));
- result.row(f.getName() + "_endpoint_verification").column(VALUE, Boolean.toString(value.require_endpoint_verification));
- result.row(f.getName() + "_optional").column(VALUE, Boolean.toString(value.isOptional()));
-
- if (value instanceof EncryptionOptions.ServerEncryptionOptions)
+ Loader loader = Properties.defaultLoader();
+ Map<String, Property> properties = loader.flatten(Config.class);
+ // only handling top-level replacements for now, previous logic was only top level so not a regression
+ Map<String, Replacement> replacements = Replacements.getNameReplacements(Config.class).get(Config.class);
+ if (replacements != null)
{
- EncryptionOptions.ServerEncryptionOptions server = (EncryptionOptions.ServerEncryptionOptions) value;
- result.row(f.getName() + "_internode_encryption").column(VALUE, server.internode_encryption.toString());
- result.row(f.getName() + "_legacy_ssl_storage_port").column(VALUE, Boolean.toString(server.legacy_ssl_storage_port_enabled));
+ for (Replacement r : replacements.values())
+ {
+ Property latest = properties.get(r.newName);
+ assert latest != null : "Unable to find replacement new name: " + r.newName;
+ Property conflict = properties.put(r.oldName, r.toProperty(latest));
+ // some configs kept the same name, but changed the type, if this is detected then rely on the replaced property
+ assert conflict == null || r.oldName.equals(r.newName) : String.format("New property %s attempted to replace %s, but this property already exists", latest.getName(), conflict.getName());
+ }
+ }
+ for (Map.Entry<String, String> e : BACKWARDS_COMPATABLE_NAMES.entrySet())
+ {
+ String oldName = e.getKey();
+ if (properties.containsKey(oldName))
+ throw new AssertionError("Name " + oldName + " is present in Config, this adds a conflict as this name had a different meaning in " + SettingsTable.class.getSimpleName());
+ String newName = e.getValue();
+ Property prop = Objects.requireNonNull(properties.get(newName), newName + " cant be found for " + oldName);
+ properties.put(oldName, Properties.rename(oldName, prop));
}
+ return properties;
}
- private void addTransparentEncryptionOptions(SimpleDataSet result, Field f)
+ /**
+ * settings table was released in 4.0 and attempted to support nested properties for a few hand selected properties.
+ * The issue is that 4.0 used '_' to seperate the names, which makes it hard to map back to the yaml names; to solve
+ * this 4.1+ uses '.' to avoid possible conflicts, this class provides mappings from old names to the '.' names.
+ *
+ * There were a handle full of properties which had custom names, names not present in the yaml, this map also
+ * fixes this and returns the proper (what is accessable via yaml) names.
+ */
+ private static Map<String, String> getBackwardsCompatableNames()
{
- Preconditions.checkArgument(TransparentDataEncryptionOptions.class.isAssignableFrom(f.getType()));
-
- TransparentDataEncryptionOptions value = (TransparentDataEncryptionOptions) getValue(f);
- result.row(f.getName() + "_enabled").column(VALUE, Boolean.toString(value.enabled));
- result.row(f.getName() + "_cipher").column(VALUE, value.cipher);
- result.row(f.getName() + "_chunk_length_kb").column(VALUE, Integer.toString(value.chunk_length_kb));
- result.row(f.getName() + "_iv_length").column(VALUE, Integer.toString(value.iv_length));
+ Map<String, String> names = new HashMap<>();
+ // Names that dont match yaml
+ names.put("audit_logging_options_logger", "audit_logging_options.logger.class_name");
+ names.put("server_encryption_options_client_auth", "server_encryption_options.require_client_auth");
+ names.put("server_encryption_options_endpoint_verification", "server_encryption_options.require_endpoint_verification");
+ names.put("server_encryption_options_legacy_ssl_storage_port", "server_encryption_options.legacy_ssl_storage_port_enabled");
+ names.put("server_encryption_options_protocol", "server_encryption_options.accepted_protocols");
+
+ // matching names
+ names.put("audit_logging_options_audit_logs_dir", "audit_logging_options.audit_logs_dir");
+ names.put("audit_logging_options_enabled", "audit_logging_options.enabled");
+ names.put("audit_logging_options_excluded_categories", "audit_logging_options.excluded_categories");
+ names.put("audit_logging_options_excluded_keyspaces", "audit_logging_options.excluded_keyspaces");
+ names.put("audit_logging_options_excluded_users", "audit_logging_options.excluded_users");
+ names.put("audit_logging_options_included_categories", "audit_logging_options.included_categories");
+ names.put("audit_logging_options_included_keyspaces", "audit_logging_options.included_keyspaces");
+ names.put("audit_logging_options_included_users", "audit_logging_options.included_users");
+ names.put("server_encryption_options_algorithm", "server_encryption_options.algorithm");
+ names.put("server_encryption_options_cipher_suites", "server_encryption_options.cipher_suites");
+ names.put("server_encryption_options_enabled", "server_encryption_options.enabled");
+ names.put("server_encryption_options_internode_encryption", "server_encryption_options.internode_encryption");
+ names.put("server_encryption_options_optional", "server_encryption_options.optional");
+ names.put("transparent_data_encryption_options_chunk_length_kb", "transparent_data_encryption_options.chunk_length_kb");
+ names.put("transparent_data_encryption_options_cipher", "transparent_data_encryption_options.cipher");
+ names.put("transparent_data_encryption_options_enabled", "transparent_data_encryption_options.enabled");
+ names.put("transparent_data_encryption_options_iv_length", "transparent_data_encryption_options.iv_length");
+
+ return names;
}
}
diff --git a/src/java/org/apache/cassandra/tools/BulkLoader.java b/src/java/org/apache/cassandra/tools/BulkLoader.java
index d1a4b8b1b6..5582524ce7 100644
--- a/src/java/org/apache/cassandra/tools/BulkLoader.java
+++ b/src/java/org/apache/cassandra/tools/BulkLoader.java
@@ -249,7 +249,7 @@ public class BulkLoader
private static SSLOptions buildSSLOptions(EncryptionOptions clientEncryptionOptions)
{
- if (!clientEncryptionOptions.isEnabled())
+ if (!clientEncryptionOptions.getEnabled())
{
return null;
}
diff --git a/src/java/org/apache/cassandra/tools/LoaderOptions.java b/src/java/org/apache/cassandra/tools/LoaderOptions.java
index 0ea3878ea3..e73f38f288 100644
--- a/src/java/org/apache/cassandra/tools/LoaderOptions.java
+++ b/src/java/org/apache/cassandra/tools/LoaderOptions.java
@@ -449,7 +449,7 @@ public class LoaderOptions
}
else
{
- if (config.native_transport_port_ssl != null && (config.client_encryption_options.isEnabled() || clientEncOptions.isEnabled()))
+ if (config.native_transport_port_ssl != null && (config.client_encryption_options.getEnabled() || clientEncOptions.getEnabled()))
nativePort = config.native_transport_port_ssl;
else
nativePort = config.native_transport_port;
diff --git a/src/java/org/apache/cassandra/transport/SimpleClient.java b/src/java/org/apache/cassandra/transport/SimpleClient.java
index 74a8b6e088..43bb8addee 100644
--- a/src/java/org/apache/cassandra/transport/SimpleClient.java
+++ b/src/java/org/apache/cassandra/transport/SimpleClient.java
@@ -222,7 +222,7 @@ public class SimpleClient implements Closeable
.option(ChannelOption.TCP_NODELAY, true);
// Configure the pipeline factory.
- if(encryptionOptions.isEnabled())
+ if(encryptionOptions.getEnabled())
{
bootstrap.handler(new SecureInitializer(largeMessageThreshold));
}
diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java
index 0a5b71f503..4fdbc9b969 100644
--- a/src/java/org/apache/cassandra/utils/FBUtilities.java
+++ b/src/java/org/apache/cassandra/utils/FBUtilities.java
@@ -1099,4 +1099,24 @@ public class FBUtilities
// ignore
}
}
+
+ public static String camelToSnake(String camel)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (char c : camel.toCharArray())
+ {
+ if (Character.isUpperCase(c))
+ {
+ // if first char is uppercase, then avoid adding the _ prefix
+ if (sb.length() > 0)
+ sb.append('_');
+ sb.append(Character.toLowerCase(c));
+ }
+ else
+ {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
}
diff --git a/test/data/config/version=3.0.0-alpha1.yml b/test/data/config/version=3.0.0-alpha1.yml
new file mode 100644
index 0000000000..4c5381bc9f
--- /dev/null
+++ b/test/data/config/version=3.0.0-alpha1.yml
@@ -0,0 +1,214 @@
+---
+trickle_fsync: "java.lang.Boolean"
+rpc_listen_backlog: "java.lang.Integer"
+max_streaming_retries: "java.lang.Integer"
+native_transport_flush_in_batches_legacy: "java.lang.Boolean"
+row_cache_save_period: "java.lang.Integer"
+rpc_address: "java.lang.String"
+disk_optimization_estimate_percentile: "java.lang.Double"
+hinted_handoff_disabled_datacenters: "java.util.Set"
+num_tokens: "java.lang.Integer"
+read_request_timeout_in_ms: "java.lang.Long"
+rpc_max_threads: "java.lang.Integer"
+enable_drop_compact_storage: "java.lang.Boolean"
+commitlog_directory: "java.lang.String"
+unlogged_batch_across_partitions_warn_threshold: "java.lang.Integer"
+auto_bootstrap: "java.lang.Boolean"
+authorizer: "java.lang.String"
+memtable_heap_space_in_mb: "java.lang.Integer"
+index_interval: "java.lang.Integer"
+sstable_preemptive_open_interval_in_mb: "java.lang.Integer"
+broadcast_rpc_address: "java.lang.String"
+commitlog_sync: "org.apache.cassandra.config.Config.CommitLogSync"
+listen_interface_prefer_ipv6: "java.lang.Boolean"
+repair_session_max_tree_depth: "java.lang.Integer"
+request_scheduler_options:
+ throttle_limit: "java.lang.Integer"
+ default_weight: "java.lang.Integer"
+ weights: "java.util.Map"
+user_defined_function_warn_timeout: "java.lang.Long"
+request_scheduler_id: "org.apache.cassandra.config.Config.RequestSchedulerId"
+tracetype_repair_ttl: "java.lang.Integer"
+rpc_send_buff_size_in_bytes: "java.lang.Integer"
+concurrent_compactors: "java.lang.Integer"
+buffer_pool_use_heap_if_exhausted: "java.lang.Boolean"
+concurrent_materialized_view_writes: "java.lang.Integer"
+commitlog_total_space_in_mb: "java.lang.Integer"
+hints_directory: "java.lang.String"
+listen_address: "java.lang.String"
+native_transport_max_concurrent_connections_per_ip: "java.lang.Long"
+rpc_keepalive: "java.lang.Boolean"
+request_scheduler: "java.lang.String"
+rpc_interface_prefer_ipv6: "java.lang.Boolean"
+check_for_duplicate_rows_during_compaction: "java.lang.Boolean"
+request_timeout_in_ms: "java.lang.Long"
+user_function_timeout_policy: "org.apache.cassandra.config.Config.UserFunctionTimeoutPolicy"
+disk_access_mode: "org.apache.cassandra.config.Config.DiskAccessMode"
+rpc_server_type: "java.lang.String"
+concurrent_counter_writes: "java.lang.Integer"
+counter_write_request_timeout_in_ms: "java.lang.Long"
+roles_update_interval_in_ms: "java.lang.Integer"
+row_cache_size_in_mb: "java.lang.Long"
+memtable_allocation_type: "org.apache.cassandra.config.Config.MemtableAllocationType"
+trickle_fsync_interval_in_kb: "java.lang.Integer"
+cas_contention_timeout_in_ms: "java.lang.Long"
+key_cache_size_in_mb: "java.lang.Long"
+tombstone_warn_threshold: "java.lang.Integer"
+min_free_space_per_drive_in_mb: "java.lang.Integer"
+write_request_timeout_in_ms: "java.lang.Long"
+cross_node_timeout: "java.lang.Boolean"
+dynamic_snitch: "java.lang.Boolean"
+permissions_validity_in_ms: "java.lang.Integer"
+phi_convict_threshold: "java.lang.Double"
+commitlog_sync_batch_window_in_ms: "java.lang.Double"
+native_transport_max_threads: "java.lang.Integer"
+thrift_max_message_length_in_mb: "java.lang.Integer"
+disk_failure_policy: "org.apache.cassandra.config.Config.DiskFailurePolicy"
+permissions_update_interval_in_ms: "java.lang.Integer"
+tombstone_failure_threshold: "java.lang.Integer"
+authenticator: "java.lang.String"
+max_mutation_size_in_kb: "java.lang.Integer"
+cache_load_timeout_seconds: "java.lang.Integer"
+initial_token: "java.lang.String"
+batch_size_warn_threshold_in_kb: "java.lang.Integer"
+concurrent_replicates: "java.lang.Integer"
+dynamic_snitch_badness_threshold: "java.lang.Double"
+index_summary_capacity_in_mb: "java.lang.Long"
+commitlog_sync_period_in_ms: "java.lang.Integer"
+counter_cache_keys_to_save: "java.lang.Integer"
+disk_optimization_page_cross_chance: "java.lang.Double"
+listen_on_broadcast_address: "java.lang.Boolean"
+native_transport_max_concurrent_requests_in_bytes: "java.lang.Long"
+rpc_min_threads: "java.lang.Integer"
+row_cache_class_name: "java.lang.String"
+gc_warn_threshold_in_ms: "java.lang.Integer"
+disk_optimization_strategy: "org.apache.cassandra.config.Config.DiskOptimizationStrategy"
+compaction_large_partition_warning_threshold_mb: "java.lang.Integer"
+enable_user_defined_functions_threads: "java.lang.Boolean"
+hinted_handoff_throttle_in_kb: "java.lang.Integer"
+otc_backlog_expiration_interval_ms: "java.lang.Integer"
+counter_cache_save_period: "java.lang.Integer"
+otc_coalescing_enough_coalesced_messages: "java.lang.Integer"
+hints_flush_period_in_ms: "java.lang.Integer"
+role_manager: "java.lang.String"
+thrift_framed_transport_size_in_mb: "java.lang.Integer"
+server_encryption_options:
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ internode_encryption: "org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ truststore: "java.lang.String"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ algorithm: "java.lang.String"
+max_hints_delivery_threads: "java.lang.Integer"
+column_index_size_in_kb: "java.lang.Integer"
+memtable_offheap_space_in_mb: "java.lang.Integer"
+data_file_directories: "java.util.List"
+saved_caches_directory: "java.lang.String"
+native_transport_max_frame_size_in_mb: "java.lang.Integer"
+index_summary_resize_interval_in_minutes: "java.lang.Integer"
+streaming_socket_timeout_in_ms: "java.lang.Integer"
+encryption_options:
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ internode_encryption: "org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ truststore: "java.lang.String"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ algorithm: "java.lang.String"
+start_rpc: "java.lang.Boolean"
+enable_user_defined_functions: "java.lang.Boolean"
+max_hint_window_in_ms: "java.lang.Integer"
+gc_log_threshold_in_ms: "java.lang.Integer"
+snapshot_on_duplicate_row_detection: "java.lang.Boolean"
+seed_provider:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+check_for_duplicate_rows_during_reads: "java.lang.Boolean"
+internode_compression: "org.apache.cassandra.config.Config.InternodeCompression"
+internode_send_buff_size_in_bytes: "java.lang.Integer"
+otc_coalescing_window_us: "java.lang.Integer"
+batchlog_replay_throttle_in_kb: "java.lang.Integer"
+enable_scripted_user_defined_functions: "java.lang.Boolean"
+commitlog_compression:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+broadcast_address: "java.lang.String"
+rpc_recv_buff_size_in_bytes: "java.lang.Integer"
+enable_materialized_views: "java.lang.Boolean"
+roles_validity_in_ms: "java.lang.Integer"
+snapshot_before_compaction: "java.lang.Boolean"
+native_transport_port_ssl: "java.lang.Integer"
+allocate_tokens_for_keyspace: "java.lang.String"
+storage_port: "java.lang.Integer"
+counter_cache_size_in_mb: "java.lang.Long"
+native_transport_port: "java.lang.Integer"
+dynamic_snitch_reset_interval_in_ms: "java.lang.Integer"
+permissions_cache_max_entries: "java.lang.Integer"
+tracetype_query_ttl: "java.lang.Integer"
+stream_throughput_outbound_megabits_per_sec: "java.lang.Integer"
+rpc_port: "java.lang.Integer"
+commit_failure_policy: "org.apache.cassandra.config.Config.CommitFailurePolicy"
+concurrent_writes: "java.lang.Integer"
+range_request_timeout_in_ms: "java.lang.Long"
+dynamic_snitch_update_interval_in_ms: "java.lang.Integer"
+hints_compression:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+commitlog_periodic_queue_size: "java.lang.Integer"
+hinted_handoff_enabled: "java.lang.Boolean"
+max_value_size_in_mb: "java.lang.Integer"
+memtable_flush_writers: "java.lang.Integer"
+otc_coalescing_strategy: "java.lang.String"
+commitlog_max_compression_buffers_in_pool: "java.lang.Integer"
+roles_cache_max_entries: "java.lang.Integer"
+native_transport_max_negotiable_protocol_version: "java.lang.Integer"
+partitioner: "java.lang.String"
+internode_recv_buff_size_in_bytes: "java.lang.Integer"
+listen_interface: "java.lang.String"
+start_native_transport: "java.lang.Boolean"
+ssl_storage_port: "java.lang.Integer"
+user_defined_function_fail_timeout: "java.lang.Long"
+cluster_name: "java.lang.String"
+incremental_backups: "java.lang.Boolean"
+file_cache_size_in_mb: "java.lang.Integer"
+inter_dc_tcp_nodelay: "java.lang.Boolean"
+internode_authenticator: "java.lang.String"
+key_cache_keys_to_save: "java.lang.Integer"
+key_cache_save_period: "java.lang.Integer"
+windows_timer_interval: "java.lang.Integer"
+rpc_interface: "java.lang.String"
+commitlog_segment_size_in_mb: "java.lang.Integer"
+row_cache_keys_to_save: "java.lang.Integer"
+replica_filtering_protection:
+ cached_rows_fail_threshold: "java.lang.Integer"
+ cached_rows_warn_threshold: "java.lang.Integer"
+native_transport_max_concurrent_requests_in_bytes_per_ip: "java.lang.Long"
+native_transport_max_concurrent_connections: "java.lang.Long"
+memtable_cleanup_threshold: "java.lang.Float"
+concurrent_reads: "java.lang.Integer"
+inter_dc_stream_throughput_outbound_megabits_per_sec: "java.lang.Integer"
+truncate_request_timeout_in_ms: "java.lang.Long"
+client_encryption_options:
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ optional: "java.lang.Boolean"
+ truststore: "java.lang.String"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ enabled: "java.lang.Boolean"
+ algorithm: "java.lang.String"
+auto_snapshot: "java.lang.Boolean"
+batch_size_fail_threshold_in_kb: "java.lang.Integer"
+compaction_throughput_mb_per_sec: "java.lang.Integer"
+max_hints_file_size_in_mb: "java.lang.Integer"
+endpoint_snitch: "java.lang.String"
diff --git a/test/data/config/version=3.11.0.yml b/test/data/config/version=3.11.0.yml
new file mode 100644
index 0000000000..9492cbfa7b
--- /dev/null
+++ b/test/data/config/version=3.11.0.yml
@@ -0,0 +1,247 @@
+---
+trickle_fsync: "java.lang.Boolean"
+rpc_listen_backlog: "java.lang.Integer"
+max_streaming_retries: "java.lang.Integer"
+cdc_total_space_in_mb: "java.lang.Integer"
+native_transport_flush_in_batches_legacy: "java.lang.Boolean"
+row_cache_save_period: "java.lang.Integer"
+rpc_address: "java.lang.String"
+disk_optimization_estimate_percentile: "java.lang.Double"
+hinted_handoff_disabled_datacenters: "java.util.Set"
+cdc_enabled: "java.lang.Boolean"
+cdc_raw_directory: "java.lang.String"
+num_tokens: "java.lang.Integer"
+read_request_timeout_in_ms: "java.lang.Long"
+rpc_max_threads: "java.lang.Integer"
+enable_drop_compact_storage: "java.lang.Boolean"
+commitlog_directory: "java.lang.String"
+unlogged_batch_across_partitions_warn_threshold: "java.lang.Integer"
+credentials_validity_in_ms: "java.lang.Integer"
+auto_bootstrap: "java.lang.Boolean"
+authorizer: "java.lang.String"
+memtable_heap_space_in_mb: "java.lang.Integer"
+index_interval: "java.lang.Integer"
+sstable_preemptive_open_interval_in_mb: "java.lang.Integer"
+broadcast_rpc_address: "java.lang.String"
+commitlog_sync: "org.apache.cassandra.config.Config.CommitLogSync"
+listen_interface_prefer_ipv6: "java.lang.Boolean"
+repair_session_max_tree_depth: "java.lang.Integer"
+request_scheduler_options:
+ throttle_limit: "java.lang.Integer"
+ default_weight: "java.lang.Integer"
+ weights: "java.util.Map"
+user_defined_function_warn_timeout: "java.lang.Long"
+request_scheduler_id: "org.apache.cassandra.config.Config.RequestSchedulerId"
+tracetype_repair_ttl: "java.lang.Integer"
+rpc_send_buff_size_in_bytes: "java.lang.Integer"
+concurrent_compactors: "java.lang.Integer"
+buffer_pool_use_heap_if_exhausted: "java.lang.Boolean"
+concurrent_materialized_view_writes: "java.lang.Integer"
+commitlog_total_space_in_mb: "java.lang.Integer"
+hints_directory: "java.lang.String"
+listen_address: "java.lang.String"
+native_transport_max_concurrent_connections_per_ip: "java.lang.Long"
+rpc_keepalive: "java.lang.Boolean"
+request_scheduler: "java.lang.String"
+allow_extra_insecure_udfs: "java.lang.Boolean"
+rpc_interface_prefer_ipv6: "java.lang.Boolean"
+check_for_duplicate_rows_during_compaction: "java.lang.Boolean"
+request_timeout_in_ms: "java.lang.Long"
+user_function_timeout_policy: "org.apache.cassandra.config.Config.UserFunctionTimeoutPolicy"
+disk_access_mode: "org.apache.cassandra.config.Config.DiskAccessMode"
+rpc_server_type: "java.lang.String"
+concurrent_counter_writes: "java.lang.Integer"
+counter_write_request_timeout_in_ms: "java.lang.Long"
+roles_update_interval_in_ms: "java.lang.Integer"
+row_cache_size_in_mb: "java.lang.Long"
+memtable_allocation_type: "org.apache.cassandra.config.Config.MemtableAllocationType"
+trickle_fsync_interval_in_kb: "java.lang.Integer"
+cas_contention_timeout_in_ms: "java.lang.Long"
+key_cache_size_in_mb: "java.lang.Long"
+tombstone_warn_threshold: "java.lang.Integer"
+column_index_cache_size_in_kb: "java.lang.Integer"
+min_free_space_per_drive_in_mb: "java.lang.Integer"
+write_request_timeout_in_ms: "java.lang.Long"
+cross_node_timeout: "java.lang.Boolean"
+dynamic_snitch: "java.lang.Boolean"
+permissions_validity_in_ms: "java.lang.Integer"
+phi_convict_threshold: "java.lang.Double"
+commitlog_sync_batch_window_in_ms: "java.lang.Double"
+native_transport_max_threads: "java.lang.Integer"
+thrift_max_message_length_in_mb: "java.lang.Integer"
+disk_failure_policy: "org.apache.cassandra.config.Config.DiskFailurePolicy"
+permissions_update_interval_in_ms: "java.lang.Integer"
+tombstone_failure_threshold: "java.lang.Integer"
+authenticator: "java.lang.String"
+max_mutation_size_in_kb: "java.lang.Integer"
+allow_insecure_udfs: "java.lang.Boolean"
+cache_load_timeout_seconds: "java.lang.Integer"
+initial_token: "java.lang.String"
+batch_size_warn_threshold_in_kb: "java.lang.Integer"
+concurrent_replicates: "java.lang.Integer"
+dynamic_snitch_badness_threshold: "java.lang.Double"
+index_summary_capacity_in_mb: "java.lang.Long"
+commitlog_sync_period_in_ms: "java.lang.Integer"
+counter_cache_keys_to_save: "java.lang.Integer"
+disk_optimization_page_cross_chance: "java.lang.Double"
+listen_on_broadcast_address: "java.lang.Boolean"
+native_transport_max_concurrent_requests_in_bytes: "java.lang.Long"
+rpc_min_threads: "java.lang.Integer"
+row_cache_class_name: "java.lang.String"
+gc_warn_threshold_in_ms: "java.lang.Integer"
+disk_optimization_strategy: "org.apache.cassandra.config.Config.DiskOptimizationStrategy"
+compaction_large_partition_warning_threshold_mb: "java.lang.Integer"
+enable_user_defined_functions_threads: "java.lang.Boolean"
+hinted_handoff_throttle_in_kb: "java.lang.Integer"
+otc_backlog_expiration_interval_ms: "java.lang.Integer"
+counter_cache_save_period: "java.lang.Integer"
+otc_coalescing_enough_coalesced_messages: "java.lang.Integer"
+slow_query_log_timeout_in_ms: "java.lang.Long"
+hints_flush_period_in_ms: "java.lang.Integer"
+role_manager: "java.lang.String"
+thrift_framed_transport_size_in_mb: "java.lang.Integer"
+server_encryption_options:
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ internode_encryption: "org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ truststore: "java.lang.String"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ require_endpoint_verification: "java.lang.Boolean"
+ algorithm: "java.lang.String"
+max_hints_delivery_threads: "java.lang.Integer"
+column_index_size_in_kb: "java.lang.Integer"
+memtable_offheap_space_in_mb: "java.lang.Integer"
+data_file_directories: "java.util.List"
+saved_caches_directory: "java.lang.String"
+native_transport_max_frame_size_in_mb: "java.lang.Integer"
+index_summary_resize_interval_in_minutes: "java.lang.Integer"
+streaming_socket_timeout_in_ms: "java.lang.Integer"
+encryption_options:
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ internode_encryption: "org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ truststore: "java.lang.String"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ require_endpoint_verification: "java.lang.Boolean"
+ algorithm: "java.lang.String"
+file_cache_round_up: "java.lang.Boolean"
+streaming_keep_alive_period_in_secs: "java.lang.Integer"
+start_rpc: "java.lang.Boolean"
+enable_user_defined_functions: "java.lang.Boolean"
+max_hint_window_in_ms: "java.lang.Integer"
+enable_sasi_indexes: "java.lang.Boolean"
+gc_log_threshold_in_ms: "java.lang.Integer"
+snapshot_on_duplicate_row_detection: "java.lang.Boolean"
+seed_provider:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+check_for_duplicate_rows_during_reads: "java.lang.Boolean"
+internode_compression: "org.apache.cassandra.config.Config.InternodeCompression"
+internode_send_buff_size_in_bytes: "java.lang.Integer"
+otc_coalescing_window_us: "java.lang.Integer"
+credentials_cache_max_entries: "java.lang.Integer"
+batchlog_replay_throttle_in_kb: "java.lang.Integer"
+enable_scripted_user_defined_functions: "java.lang.Boolean"
+commitlog_compression:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+broadcast_address: "java.lang.String"
+rpc_recv_buff_size_in_bytes: "java.lang.Integer"
+credentials_update_interval_in_ms: "java.lang.Integer"
+enable_materialized_views: "java.lang.Boolean"
+roles_validity_in_ms: "java.lang.Integer"
+snapshot_before_compaction: "java.lang.Boolean"
+back_pressure_strategy:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+prepared_statements_cache_size_mb: "java.lang.Long"
+native_transport_port_ssl: "java.lang.Integer"
+allocate_tokens_for_keyspace: "java.lang.String"
+storage_port: "java.lang.Integer"
+counter_cache_size_in_mb: "java.lang.Long"
+native_transport_port: "java.lang.Integer"
+dynamic_snitch_reset_interval_in_ms: "java.lang.Integer"
+permissions_cache_max_entries: "java.lang.Integer"
+tracetype_query_ttl: "java.lang.Integer"
+stream_throughput_outbound_megabits_per_sec: "java.lang.Integer"
+rpc_port: "java.lang.Integer"
+commit_failure_policy: "org.apache.cassandra.config.Config.CommitFailurePolicy"
+concurrent_writes: "java.lang.Integer"
+range_request_timeout_in_ms: "java.lang.Long"
+dynamic_snitch_update_interval_in_ms: "java.lang.Integer"
+hints_compression:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+commitlog_periodic_queue_size: "java.lang.Integer"
+force_new_prepared_statement_behaviour: "java.lang.Boolean"
+hinted_handoff_enabled: "java.lang.Boolean"
+back_pressure_enabled: "java.lang.Boolean"
+max_value_size_in_mb: "java.lang.Integer"
+memtable_flush_writers: "java.lang.Integer"
+otc_coalescing_strategy: "java.lang.String"
+commitlog_max_compression_buffers_in_pool: "java.lang.Integer"
+roles_cache_max_entries: "java.lang.Integer"
+cdc_free_space_check_interval_ms: "java.lang.Integer"
+native_transport_max_negotiable_protocol_version: "java.lang.Integer"
+transparent_data_encryption_options:
+ cipher: "java.lang.String"
+ chunk_length_kb: "java.lang.Integer"
+ iv_length: "java.lang.Integer"
+ key_alias: "java.lang.String"
+ key_provider:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+ enabled: "java.lang.Boolean"
+partitioner: "java.lang.String"
+internode_recv_buff_size_in_bytes: "java.lang.Integer"
+listen_interface: "java.lang.String"
+start_native_transport: "java.lang.Boolean"
+ssl_storage_port: "java.lang.Integer"
+user_defined_function_fail_timeout: "java.lang.Long"
+cluster_name: "java.lang.String"
+incremental_backups: "java.lang.Boolean"
+file_cache_size_in_mb: "java.lang.Integer"
+inter_dc_tcp_nodelay: "java.lang.Boolean"
+internode_authenticator: "java.lang.String"
+key_cache_keys_to_save: "java.lang.Integer"
+key_cache_save_period: "java.lang.Integer"
+windows_timer_interval: "java.lang.Integer"
+rpc_interface: "java.lang.String"
+commitlog_segment_size_in_mb: "java.lang.Integer"
+row_cache_keys_to_save: "java.lang.Integer"
+replica_filtering_protection:
+ cached_rows_fail_threshold: "java.lang.Integer"
+ cached_rows_warn_threshold: "java.lang.Integer"
+native_transport_max_concurrent_requests_in_bytes_per_ip: "java.lang.Long"
+native_transport_max_concurrent_connections: "java.lang.Long"
+memtable_cleanup_threshold: "java.lang.Float"
+concurrent_reads: "java.lang.Integer"
+inter_dc_stream_throughput_outbound_megabits_per_sec: "java.lang.Integer"
+thrift_prepared_statements_cache_size_mb: "java.lang.Long"
+truncate_request_timeout_in_ms: "java.lang.Long"
+client_encryption_options:
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ optional: "java.lang.Boolean"
+ truststore: "java.lang.String"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ enabled: "java.lang.Boolean"
+ require_endpoint_verification: "java.lang.Boolean"
+ algorithm: "java.lang.String"
+auto_snapshot: "java.lang.Boolean"
+batch_size_fail_threshold_in_kb: "java.lang.Integer"
+compaction_throughput_mb_per_sec: "java.lang.Integer"
+max_hints_file_size_in_mb: "java.lang.Integer"
+endpoint_snitch: "java.lang.String"
diff --git a/test/data/config/version=4.0-alpha1.yml b/test/data/config/version=4.0-alpha1.yml
new file mode 100644
index 0000000000..2a0c58ee3e
--- /dev/null
+++ b/test/data/config/version=4.0-alpha1.yml
@@ -0,0 +1,301 @@
+---
+repaired_data_tracking_for_range_reads_enabled: "java.lang.Boolean"
+block_for_peers_timeout_in_secs: "java.lang.Integer"
+flush_compression: "org.apache.cassandra.config.Config.FlushCompression"
+audit_logging_options:
+ audit_logs_dir: "java.lang.String"
+ included_users: "java.lang.String"
+ logger:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+ excluded_categories: "java.lang.String"
+ roll_cycle: "java.lang.String"
+ enabled: "java.lang.Boolean"
+ included_categories: "java.lang.String"
+ max_archive_retries: "java.lang.Integer"
+ excluded_keyspaces: "java.lang.String"
+ archive_command: "java.lang.String"
+ included_keyspaces: "java.lang.String"
+ max_log_size: "java.lang.Long"
+ block: "java.lang.Boolean"
+ excluded_users: "java.lang.String"
+ max_queue_weight: "java.lang.Integer"
+cdc_total_space_in_mb: "java.lang.Integer"
+internode_application_send_queue_reserve_global_capacity_in_bytes: "java.lang.Integer"
+row_cache_save_period: "java.lang.Integer"
+snapshot_links_per_second: "java.lang.Long"
+disk_optimization_estimate_percentile: "java.lang.Double"
+hinted_handoff_disabled_datacenters: "java.util.Set"
+cdc_enabled: "java.lang.Boolean"
+read_request_timeout_in_ms: "java.lang.Long"
+internode_application_receive_queue_reserve_global_capacity_in_bytes: "java.lang.Integer"
+credentials_validity_in_ms: "java.lang.Integer"
+memtable_heap_space_in_mb: "java.lang.Integer"
+commitlog_sync: "org.apache.cassandra.config.Config.CommitLogSync"
+user_defined_function_warn_timeout: "java.lang.Long"
+tracetype_repair_ttl: "java.lang.Integer"
+concurrent_materialized_view_writes: "java.lang.Integer"
+commitlog_total_space_in_mb: "java.lang.Integer"
+hints_directory: "java.lang.String"
+native_transport_max_concurrent_connections_per_ip: "java.lang.Long"
+internode_socket_send_buffer_size_in_bytes: "java.lang.Integer"
+rpc_interface_prefer_ipv6: "java.lang.Boolean"
+check_for_duplicate_rows_during_compaction: "java.lang.Boolean"
+internode_socket_receive_buffer_size_in_bytes: "java.lang.Integer"
+user_function_timeout_policy: "org.apache.cassandra.config.Config.UserFunctionTimeoutPolicy"
+counter_write_request_timeout_in_ms: "java.lang.Long"
+roles_update_interval_in_ms: "java.lang.Integer"
+memtable_allocation_type: "org.apache.cassandra.config.Config.MemtableAllocationType"
+trickle_fsync_interval_in_kb: "java.lang.Integer"
+enable_transient_replication: "java.lang.Boolean"
+key_cache_size_in_mb: "java.lang.Long"
+tombstone_warn_threshold: "java.lang.Integer"
+column_index_cache_size_in_kb: "java.lang.Integer"
+full_query_logging_options:
+ log_dir: "java.lang.String"
+ archive_command: "java.lang.String"
+ max_log_size: "java.lang.Long"
+ block: "java.lang.Boolean"
+ roll_cycle: "java.lang.String"
+ max_queue_weight: "java.lang.Integer"
+ max_archive_retries: "java.lang.Integer"
+table_count_warn_threshold: "java.lang.Integer"
+write_request_timeout_in_ms: "java.lang.Long"
+internode_tcp_user_timeout_in_ms: "java.lang.Integer"
+auto_optimise_inc_repair_streams: "java.lang.Boolean"
+commitlog_sync_batch_window_in_ms: "java.lang.Double"
+disk_failure_policy: "org.apache.cassandra.config.Config.DiskFailurePolicy"
+tombstone_failure_threshold: "java.lang.Integer"
+validation_preview_purge_head_start_in_sec: "java.lang.Integer"
+max_mutation_size_in_kb: "java.lang.Integer"
+initial_token: "java.lang.String"
+batch_size_warn_threshold_in_kb: "java.lang.Integer"
+dynamic_snitch_badness_threshold: "java.lang.Double"
+index_summary_capacity_in_mb: "java.lang.Long"
+allocate_tokens_for_local_replication_factor: "java.lang.Integer"
+counter_cache_keys_to_save: "java.lang.Integer"
+disk_optimization_page_cross_chance: "java.lang.Double"
+listen_on_broadcast_address: "java.lang.Boolean"
+internode_application_receive_queue_reserve_endpoint_capacity_in_bytes: "java.lang.Integer"
+row_cache_class_name: "java.lang.String"
+gc_warn_threshold_in_ms: "java.lang.Integer"
+disk_optimization_strategy: "org.apache.cassandra.config.Config.DiskOptimizationStrategy"
+hinted_handoff_throttle_in_kb: "java.lang.Integer"
+otc_backlog_expiration_interval_ms: "java.lang.Integer"
+counter_cache_save_period: "java.lang.Integer"
+keyspace_count_warn_threshold: "java.lang.Integer"
+hints_flush_period_in_ms: "java.lang.Integer"
+role_manager: "java.lang.String"
+block_for_peers_in_remote_dcs: "java.lang.Boolean"
+repair_command_pool_size: "java.lang.Integer"
+column_index_size_in_kb: "java.lang.Integer"
+memtable_offheap_space_in_mb: "java.lang.Integer"
+data_file_directories: "java.util.List"
+native_transport_max_frame_size_in_mb: "java.lang.Integer"
+index_summary_resize_interval_in_minutes: "java.lang.Integer"
+enable_user_defined_functions: "java.lang.Boolean"
+max_hint_window_in_ms: "java.lang.Integer"
+seed_provider:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+check_for_duplicate_rows_during_reads: "java.lang.Boolean"
+key_cache_migrate_during_compaction: "java.lang.Boolean"
+network_authorizer: "java.lang.String"
+batchlog_replay_throttle_in_kb: "java.lang.Integer"
+enable_scripted_user_defined_functions: "java.lang.Boolean"
+internode_application_send_queue_reserve_endpoint_capacity_in_bytes: "java.lang.Integer"
+commitlog_compression:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+broadcast_address: "java.lang.String"
+credentials_update_interval_in_ms: "java.lang.Integer"
+snapshot_before_compaction: "java.lang.Boolean"
+back_pressure_strategy:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+prepared_statements_cache_size_mb: "java.lang.Long"
+native_transport_port_ssl: "java.lang.Integer"
+allocate_tokens_for_keyspace: "java.lang.String"
+diagnostic_events_enabled: "java.lang.Boolean"
+storage_port: "java.lang.Integer"
+counter_cache_size_in_mb: "java.lang.Long"
+dynamic_snitch_reset_interval_in_ms: "java.lang.Integer"
+tracetype_query_ttl: "java.lang.Integer"
+autocompaction_on_startup_enabled: "java.lang.Boolean"
+commit_failure_policy: "org.apache.cassandra.config.Config.CommitFailurePolicy"
+concurrent_writes: "java.lang.Integer"
+range_request_timeout_in_ms: "java.lang.Long"
+dynamic_snitch_update_interval_in_ms: "java.lang.Integer"
+hinted_handoff_enabled: "java.lang.Boolean"
+internode_application_receive_queue_capacity_in_bytes: "java.lang.Integer"
+automatic_sstable_upgrade: "java.lang.Boolean"
+max_value_size_in_mb: "java.lang.Integer"
+memtable_flush_writers: "java.lang.Integer"
+otc_coalescing_strategy: "java.lang.String"
+snapshot_on_repaired_data_mismatch: "java.lang.Boolean"
+commitlog_max_compression_buffers_in_pool: "java.lang.Integer"
+internode_application_send_queue_capacity_in_bytes: "java.lang.Integer"
+roles_cache_max_entries: "java.lang.Integer"
+native_transport_max_negotiable_protocol_version: "java.lang.Integer"
+start_native_transport: "java.lang.Boolean"
+ssl_storage_port: "java.lang.Integer"
+cluster_name: "java.lang.String"
+incremental_backups: "java.lang.Boolean"
+key_cache_save_period: "java.lang.Integer"
+windows_timer_interval: "java.lang.Integer"
+rpc_interface: "java.lang.String"
+repair_session_space_in_mb: "java.lang.Integer"
+row_cache_keys_to_save: "java.lang.Integer"
+repair_command_pool_full_strategy: "org.apache.cassandra.config.Config.RepairCommandPoolFullStrategy"
+inter_dc_stream_throughput_outbound_megabits_per_sec: "java.lang.Integer"
+client_encryption_options:
+ optional: "java.lang.Boolean"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ enabled: "java.lang.Boolean"
+ require_endpoint_verification: "java.lang.Boolean"
+ accepted_protocols: "java.util.List"
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ truststore: "java.lang.String"
+ algorithm: "java.lang.String"
+concurrent_validations: "java.lang.Integer"
+ideal_consistency_level: "org.apache.cassandra.db.ConsistencyLevel"
+consecutive_message_errors_threshold: "java.lang.Integer"
+trickle_fsync: "java.lang.Boolean"
+reject_repair_compaction_threshold: "java.lang.Integer"
+max_streaming_retries: "java.lang.Integer"
+native_transport_flush_in_batches_legacy: "java.lang.Boolean"
+rpc_address: "java.lang.String"
+file_cache_enabled: "java.lang.Boolean"
+cdc_raw_directory: "java.lang.String"
+num_tokens: "java.lang.Integer"
+repaired_data_tracking_for_partition_reads_enabled: "java.lang.Boolean"
+enable_drop_compact_storage: "java.lang.Boolean"
+commitlog_directory: "java.lang.String"
+unlogged_batch_across_partitions_warn_threshold: "java.lang.Integer"
+auto_bootstrap: "java.lang.Boolean"
+authorizer: "java.lang.String"
+sstable_preemptive_open_interval_in_mb: "java.lang.Integer"
+broadcast_rpc_address: "java.lang.String"
+listen_interface_prefer_ipv6: "java.lang.Boolean"
+repair_session_max_tree_depth: "java.lang.Integer"
+auto_optimise_preview_repair_streams: "java.lang.Boolean"
+concurrent_compactors: "java.lang.Integer"
+buffer_pool_use_heap_if_exhausted: "java.lang.Boolean"
+local_system_data_file_directory: "java.lang.String"
+stream_entire_sstables: "java.lang.Boolean"
+corrupted_tombstone_strategy: "org.apache.cassandra.config.Config.CorruptedTombstoneStrategy"
+listen_address: "java.lang.String"
+rpc_keepalive: "java.lang.Boolean"
+allow_extra_insecure_udfs: "java.lang.Boolean"
+request_timeout_in_ms: "java.lang.Long"
+disk_access_mode: "org.apache.cassandra.config.Config.DiskAccessMode"
+concurrent_counter_writes: "java.lang.Integer"
+row_cache_size_in_mb: "java.lang.Long"
+cas_contention_timeout_in_ms: "java.lang.Long"
+min_free_space_per_drive_in_mb: "java.lang.Integer"
+cross_node_timeout: "java.lang.Boolean"
+dynamic_snitch: "java.lang.Boolean"
+permissions_validity_in_ms: "java.lang.Integer"
+phi_convict_threshold: "java.lang.Double"
+native_transport_max_threads: "java.lang.Integer"
+permissions_update_interval_in_ms: "java.lang.Integer"
+authenticator: "java.lang.String"
+allow_insecure_udfs: "java.lang.Boolean"
+cache_load_timeout_seconds: "java.lang.Integer"
+concurrent_replicates: "java.lang.Integer"
+commitlog_sync_period_in_ms: "java.lang.Integer"
+auto_optimise_full_repair_streams: "java.lang.Boolean"
+internode_max_message_size_in_bytes: "java.lang.Integer"
+native_transport_max_concurrent_requests_in_bytes: "java.lang.Long"
+compaction_large_partition_warning_threshold_mb: "java.lang.Integer"
+enable_user_defined_functions_threads: "java.lang.Boolean"
+native_transport_allow_older_protocols: "java.lang.Boolean"
+otc_coalescing_enough_coalesced_messages: "java.lang.Integer"
+slow_query_log_timeout_in_ms: "java.lang.Long"
+report_unconfirmed_repaired_data_mismatches: "java.lang.Boolean"
+use_offheap_merkle_trees: "java.lang.Boolean"
+concurrent_materialized_view_builders: "java.lang.Integer"
+server_encryption_options:
+ enable_legacy_ssl_storage_port: "java.lang.Boolean"
+ optional: "java.lang.Boolean"
+ store_type: "java.lang.String"
+ cipher_suites: "java.util.List"
+ enabled: "java.lang.Boolean"
+ require_endpoint_verification: "java.lang.Boolean"
+ accepted_protocols: "java.util.List"
+ keystore_password: "java.lang.String"
+ protocol: "java.lang.String"
+ require_client_auth: "java.lang.Boolean"
+ internode_encryption: "org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption"
+ truststore_password: "java.lang.String"
+ keystore: "java.lang.String"
+ truststore: "java.lang.String"
+ algorithm: "java.lang.String"
+max_hints_delivery_threads: "java.lang.Integer"
+native_transport_idle_timeout_in_ms: "java.lang.Long"
+saved_caches_directory: "java.lang.String"
+max_concurrent_automatic_sstable_upgrades: "java.lang.Integer"
+file_cache_round_up: "java.lang.Boolean"
+streaming_keep_alive_period_in_secs: "java.lang.Integer"
+enable_sasi_indexes: "java.lang.Boolean"
+gc_log_threshold_in_ms: "java.lang.Integer"
+snapshot_on_duplicate_row_detection: "java.lang.Boolean"
+commitlog_sync_group_window_in_ms: "java.lang.Double"
+internode_compression: "org.apache.cassandra.config.Config.InternodeCompression"
+otc_coalescing_window_us: "java.lang.Integer"
+credentials_cache_max_entries: "java.lang.Integer"
+periodic_commitlog_sync_lag_block_in_ms: "java.lang.Integer"
+enable_materialized_views: "java.lang.Boolean"
+roles_validity_in_ms: "java.lang.Integer"
+networking_cache_size_in_mb: "java.lang.Integer"
+native_transport_port: "java.lang.Integer"
+permissions_cache_max_entries: "java.lang.Integer"
+stream_throughput_outbound_megabits_per_sec: "java.lang.Integer"
+hints_compression:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+commitlog_periodic_queue_size: "java.lang.Integer"
+force_new_prepared_statement_behaviour: "java.lang.Boolean"
+back_pressure_enabled: "java.lang.Boolean"
+cdc_free_space_check_interval_ms: "java.lang.Integer"
+transparent_data_encryption_options:
+ cipher: "java.lang.String"
+ chunk_length_kb: "java.lang.Integer"
+ iv_length: "java.lang.Integer"
+ key_alias: "java.lang.String"
+ key_provider:
+ class_name: "java.lang.String"
+ parameters: "java.util.Map"
+ enabled: "java.lang.Boolean"
+initial_range_tombstone_list_allocation_size: "java.lang.Integer"
+partitioner: "java.lang.String"
+listen_interface: "java.lang.String"
+user_defined_function_fail_timeout: "java.lang.Long"
+file_cache_size_in_mb: "java.lang.Integer"
+inter_dc_tcp_nodelay: "java.lang.Boolean"
+internode_authenticator: "java.lang.String"
+key_cache_keys_to_save: "java.lang.Integer"
+commitlog_segment_size_in_mb: "java.lang.Integer"
+replica_filtering_protection:
+ cached_rows_fail_threshold: "java.lang.Integer"
+ cached_rows_warn_threshold: "java.lang.Integer"
+internode_tcp_connect_timeout_in_ms: "java.lang.Integer"
+native_transport_max_concurrent_requests_in_bytes_per_ip: "java.lang.Long"
+range_tombstone_list_growth_factor: "java.lang.Double"
+native_transport_max_concurrent_connections: "java.lang.Long"
+memtable_cleanup_threshold: "java.lang.Float"
+concurrent_reads: "java.lang.Integer"
+streaming_connections_per_host: "java.lang.Integer"
+truncate_request_timeout_in_ms: "java.lang.Long"
+auto_snapshot: "java.lang.Boolean"
+native_transport_receive_queue_capacity_in_bytes: "java.lang.Integer"
+internode_streaming_tcp_user_timeout_in_ms: "java.lang.Integer"
+batch_size_fail_threshold_in_kb: "java.lang.Integer"
+compaction_throughput_mb_per_sec: "java.lang.Integer"
+max_hints_file_size_in_mb: "java.lang.Integer"
+endpoint_snitch: "java.lang.String"
diff --git a/test/distributed/org/apache/cassandra/distributed/upgrade/ConfigCompatabilityTestGenerate.java b/test/distributed/org/apache/cassandra/distributed/upgrade/ConfigCompatabilityTestGenerate.java
new file mode 100644
index 0000000000..518fd14564
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/ConfigCompatabilityTestGenerate.java
@@ -0,0 +1,63 @@
+/*
+ * 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.distributed.upgrade;
+
+import java.io.File;
+import java.util.Arrays;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import com.vdurmont.semver4j.Semver;
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.distributed.UpgradeableCluster;
+import org.apache.cassandra.distributed.api.ICluster;
+import org.apache.cassandra.distributed.api.IInvokableInstance;
+import org.apache.cassandra.distributed.impl.AbstractCluster;
+import org.apache.cassandra.distributed.shared.Versions;
+
+import static org.apache.cassandra.config.ConfigCompatabilityTest.TEST_DIR;
+import static org.apache.cassandra.config.ConfigCompatabilityTest.dump;
+import static org.apache.cassandra.config.ConfigCompatabilityTest.toTree;
+
+/**
+ * This class is to generate YAML dumps per version, this is a manual process and should be updated for each release.
+ */
+public class ConfigCompatabilityTestGenerate
+{
+ public static void main(String[] args) throws Throwable
+ {
+ ICluster.setup();
+ Versions versions = Versions.find();
+ for (Semver version : Arrays.asList(UpgradeTestBase.v30, UpgradeTestBase.v3X, UpgradeTestBase.v40))
+ {
+ File path = new File(TEST_DIR, "version=" + version + ".yml");
+ path.getParentFile().mkdirs();
+ Versions.Version latest = versions.getLatest(version);
+ // this class isn't present so the lambda can't be deserialized... so add to the classpath
+ latest = new Versions.Version(latest.version, ArrayUtils.addAll(latest.classpath, AbstractCluster.CURRENT_VERSION.classpath));
+
+ try (UpgradeableCluster cluster = UpgradeableCluster.create(1, latest))
+ {
+ IInvokableInstance inst = (IInvokableInstance) cluster.get(1);
+ Class<?> klass = inst.callOnInstance(() -> Config.class);
+ assert klass.getClassLoader() != ConfigCompatabilityTestGenerate.class.getClassLoader();
+ dump(toTree(klass), path.getAbsolutePath());
+ }
+ }
+ }
+}
diff --git a/test/unit/org/apache/cassandra/config/ConfigCompatabilityTest.java b/test/unit/org/apache/cassandra/config/ConfigCompatabilityTest.java
new file mode 100644
index 0000000000..8d1e5e237e
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/ConfigCompatabilityTest.java
@@ -0,0 +1,435 @@
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import org.yaml.snakeyaml.introspector.Property;
+
+/**
+ * To create the test files used by this class, run {@link org.apache.cassandra.distributed.upgrade.ConfigCompatabilityTestGenerate}.
+ */
+public class ConfigCompatabilityTest
+{
+ private static final Logger logger = LoggerFactory.getLogger(ConfigCompatabilityTest.class);
+
+ public static final String TEST_DIR = "test/data/config";
+
+ // see CASSANDRA-11115
+ private static final Set<String> THRIFT = ImmutableSet.of("rpc_server_type",
+ "rpc_port",
+ "rpc_listen_backlog",
+ "start_rpc",
+ "thrift_max_message_length_in_mb",
+ "rpc_max_threads",
+ "rpc_min_threads",
+ "rpc_recv_buff_size_in_bytes",
+ "rpc_send_buff_size_in_bytes",
+ "thrift_framed_transport_size_in_mb",
+ "thrift_prepared_statements_cache_size_mb",
+ "request_scheduler",
+ "request_scheduler_id",
+ "request_scheduler_options");
+ // see // CASSANDRA-16956
+ private static final Set<String> WINDOWS = ImmutableSet.of("windows_timer_interval");
+
+ private static final Set<String> REMOVED_IN_40 = ImmutableSet.<String>builder()
+ .addAll(THRIFT)
+ .addAll(WINDOWS) // windows was removed later, but support was dropped in 4.0
+ .add("encryption_options") // CASSANDRA-10404
+ .add("index_interval") // CASSANDRA-10671
+ .add("streaming_socket_timeout_in_ms") // CASSANDRA-12229
+ .build();
+
+ /**
+ * Not all converts make sense as backwards compatible as they use things like String to handle the conversion more
+ * generically.
+ */
+ private static final Set<Converters> IGNORED_CONVERTERS = EnumSet.of(Converters.SECONDS_CUSTOM_DURATION);
+
+ @Test
+ public void diff_3_0() throws IOException
+ {
+ diff(TEST_DIR + "/version=3.0.0-alpha1.yml", REMOVED_IN_40);
+ }
+
+ @Test
+ public void diff_3_11() throws IOException
+ {
+ diff(TEST_DIR + "/version=3.11.0.yml", REMOVED_IN_40);
+ }
+
+ @Test
+ public void diff_4_0() throws IOException
+ {
+ diff(TEST_DIR + "/version=4.0-alpha1.yml", ImmutableSet.<String>builder()
+ .addAll(WINDOWS)
+ .build());
+ }
+
+ private void diff(String original, Set<String> ignore) throws IOException
+ {
+ Class<Config> type = Config.class;
+ ClassTree previous = load(original);
+ Loader loader = Properties.defaultLoader();
+ Map<Class<?>, Map<String, Replacement>> replacements = Replacements.getNameReplacements(type);
+ Set<String> missing = new HashSet<>();
+ Set<String> errors = new HashSet<>();
+ diff(loader, replacements, previous, type, "", missing, errors);
+ missing = Sets.difference(missing, ignore);
+ StringBuilder msg = new StringBuilder();
+ if (!missing.isEmpty())
+ msg.append(String.format("Unable to find the following properties:\n%s", String.join("\n", new TreeSet<>(missing))));
+ if (!errors.isEmpty())
+ {
+ if (msg.length() > 0)
+ msg.append('\n');
+ msg.append(String.format("Errors detected:\n%s", String.join("\n", new TreeSet<>(errors))));
+ }
+ if (msg.length() > 0)
+ throw new AssertionError(msg);
+ }
+
+ private void diff(Loader loader, Map<Class<?>, Map<String, Replacement>> replacements, ClassTree previous, Class<?> type, String prefix, Set<String> missing, Set<String> errors)
+ {
+ Map<String, Replacement> replaces = replacements.getOrDefault(type, Collections.emptyMap());
+ Map<String, Property> properties = loader.getProperties(type);
+ Sets.SetView<String> missingInCurrent = Sets.difference(previous.properties.keySet(), properties.keySet());
+ Sets.SetView<String> inBoth = Sets.intersection(previous.properties.keySet(), properties.keySet());
+ for (String name : missingInCurrent)
+ {
+ Replacement replacement = replaces.get(name);
+ // can we find the property in @Replaces?
+ if (replacement == null)
+ {
+ missing.add(prefix + name);
+ }
+ else
+ {
+ // do types match?
+ Node node = previous.properties.get(name);
+ if (node instanceof Leaf && replacement.oldType != null)
+ typeCheck(replacement.converter, toString(replacement.oldType), ((Leaf) node).type, name, errors);
+ }
+ }
+ for (String name : inBoth)
+ {
+ Property prop = properties.get(name);
+ Node node = previous.properties.get(name);
+ // do types match?
+ // if nested, look at sub-fields
+ if (node instanceof ClassTree)
+ {
+ // current is nested type
+ diff(loader, replacements, (ClassTree) node, prop.getType(), prefix + name + ".", missing, errors);
+ }
+ else
+ {
+ // current is flat type
+ Replacement replacement = replaces.get(name);
+ if (replacement != null && replacement.oldType != null)
+ {
+ typeCheck(replacement.converter, toString(replacement.oldType), ((Leaf) node).type, name, errors);
+ }
+ else
+ {
+ // previous is leaf, is current?
+ Map<String, Property> children = Properties.isPrimitive(prop) || Properties.isCollection(prop) ? Collections.emptyMap() : loader.getProperties(prop.getType());
+ if (!children.isEmpty())
+ errors.add(String.format("Property %s used to be a value-type, but now is nested type %s", name, prop.getType()));
+ typeCheck(null, toString(prop.getType()), ((Leaf) node).type, name, errors);
+ }
+ }
+ }
+ }
+
+ private static void typeCheck(Converters converters, String lhs, String rhs, String name, Set<String> errors)
+ {
+ if (IGNORED_CONVERTERS.contains(converters))
+ return;
+ if (!lhs.equals(rhs))
+ errors.add(String.format("%s types do not match; %s != %s%s", name, lhs, rhs, converters != null ? ", converter " + converters.name() : ""));
+ }
+
+ private static ClassTree load(String path) throws IOException
+ {
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ return mapper.readValue(new File(path), ClassTree.class);
+ }
+
+ public static void dump(ClassTree classTree, String path) throws IOException
+ {
+ logger.info("Dumping class to {}", path);
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ mapper.writeValue(new File(path), classTree);
+
+ // validate that load works as expected
+ ClassTree loaded = load(path);
+ assert loaded.equals(classTree);
+ }
+
+ public static ClassTree toTree(Class<?> klass)
+ {
+ ClassTree node = new ClassTree(klass);
+ addProperties(Properties.defaultLoader(), node, klass);
+ return node;
+ }
+
+ private static void addProperties(Loader loader, ClassTree node, Class<?> type)
+ {
+ SortedMap<String, Property> properties = new TreeMap<>(loader.getProperties(type));
+ for (Map.Entry<String, Property> e : properties.entrySet())
+ {
+ Property property = e.getValue();
+ Map<String, Property> subProperties = Properties.isPrimitive(property) || Properties.isCollection(property) ? Collections.emptyMap() : loader.getProperties(property.getType());
+ Node child;
+ if (subProperties.isEmpty())
+ {
+ child = new Leaf(toString(property.getType()));
+ }
+ else
+ {
+ ClassTree subTree = new ClassTree(property.getType());
+ addProperties(loader, subTree, property.getType());
+ child = subTree;
+ }
+ node.addProperty(e.getKey(), child);
+ }
+ }
+
+ private static String toString(Class<?> type)
+ {
+ return normalize(type).getCanonicalName();
+ }
+
+ private static Class<?> normalize(Class<?> type)
+ {
+ // convert primitives to Number, allowing null in the doamin
+ // this means that switching between int to Integer, and Integer to int are seen as the same while diffing; null
+ // added/removed from domain is ignored by diff
+ if (type.equals(Byte.TYPE))
+ return Byte.class;
+ else if (type.equals(Short.TYPE))
+ return Short.class;
+ else if (type.equals(Integer.TYPE))
+ return Integer.class;
+ else if (type.equals(Long.TYPE))
+ return Long.class;
+ else if (type.equals(Float.TYPE))
+ return Float.class;
+ else if (type.equals(Double.TYPE))
+ return Double.class;
+ else if (type.equals(Boolean.TYPE))
+ return Boolean.class;
+ else if (type.isArray())
+ return List.class;
+ return type;
+ }
+
+ @JsonSerialize(using = NodeSerializer.class)
+ @JsonDeserialize(using = NodeDeserializer.class)
+ private interface Node
+ {
+
+ }
+
+ private static final class NodeSerializer extends StdSerializer<Node>
+ {
+ NodeSerializer()
+ {
+ super(Node.class);
+ }
+
+ @Override
+ public void serialize(Node node, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException
+ {
+ if (node instanceof Leaf)
+ {
+ jsonGenerator.writeString(((Leaf) node).type);
+ }
+ else if (node instanceof ClassTree)
+ {
+ ClassTree classTree = (ClassTree) node;
+ jsonGenerator.writeStartObject();
+ for (Map.Entry<String, Node> e : classTree.properties.entrySet())
+ jsonGenerator.writeObjectField(e.getKey(), e.getValue());
+ jsonGenerator.writeEndObject();
+ }
+ }
+ }
+
+ private static final class NodeDeserializer extends StdDeserializer<Node>
+ {
+
+ protected NodeDeserializer()
+ {
+ super(Node.class);
+ }
+
+ @Override
+ public Node deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException
+ {
+ return toNode(jsonParser.getCodec().readTree(jsonParser));
+ }
+
+ private static Node toNode(TreeNode node)
+ {
+ if (node.isValueNode())
+ return new Leaf(((TextNode) node).textValue());
+ Map<String, Node> props = new HashMap<>();
+ Iterator<String> it = node.fieldNames();
+ while (it.hasNext())
+ {
+ String name = it.next();
+ Node value = toNode(node.get(name));
+ Node previous = props.put(name, value);
+ if (previous != null)
+ throw new AssertionError("Duplicate properties found: " + name);
+ }
+ ClassTree classTree = new ClassTree();
+ classTree.setProperties(props);
+ return classTree;
+ }
+ }
+
+ private static class ClassTree implements Node
+ {
+ private Class<?> type = null;
+ private Map<String, Node> properties = new HashMap<>();
+
+ ClassTree()
+ {
+
+ }
+
+ ClassTree(Class<?> type)
+ {
+ this.type = type;
+ }
+
+ public Map<String, Node> getProperties()
+ {
+ return properties;
+ }
+
+ public void setProperties(Map<String, Node> properties)
+ {
+ this.properties = properties;
+ }
+
+ public void addProperty(String key, Node node)
+ {
+ Node previous = properties.put(key, node);
+ if (previous != null)
+ throw new AssertionError("Duplicate property name: " + key);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ClassTree classTree = (ClassTree) o;
+ return Objects.equals(properties, classTree.properties);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(properties);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Klass{" +
+ "type=" + type +
+ ", properties=" + properties +
+ '}';
+ }
+ }
+
+ private static class Leaf implements Node
+ {
+ private final String type;
+
+ public Leaf(String type)
+ {
+ this.type = type;
+ }
+
+ @JsonValue
+ public String getType()
+ {
+ return type;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Leaf leaf = (Leaf) o;
+ return Objects.equals(type, leaf.type);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(type);
+ }
+
+ @Override
+ public String toString()
+ {
+ return type;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/config/DefaultLoaderTest.java b/test/unit/org/apache/cassandra/config/DefaultLoaderTest.java
new file mode 100644
index 0000000000..6e122f8d85
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/DefaultLoaderTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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 org.junit.Test;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.yaml.snakeyaml.introspector.FieldProperty;
+import org.yaml.snakeyaml.introspector.MethodProperty;
+import org.yaml.snakeyaml.introspector.Property;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultLoaderTest
+{
+ private static final DefaultLoader LOADER = new DefaultLoader();
+
+ // field, no getter, no setter -> Example0
+ // field, no/ignored getter, setter -> Example1 && Example5
+ // field, getter, no/ignored setter -> Example3 && Example4
+ // field, getter, setter -> Example2
+
+ // no field, no getter, setter -> Example9 && Example10
+ // no field, getter, no/ignored setter -> Example7 && Example8
+ // no field, getter, setter -> Example6
+
+ @Test
+ public void fieldPresentWithoutGetterOrSetter()
+ {
+ // when the field is present and no getter, then the field property is used
+ assertThat(get(Example0.class, "first")).isInstanceOf(FieldProperty.class);
+ }
+
+ @Test
+ public void fieldPresentWithoutGetter()
+ {
+ // when the field is present and no getter, then the field property is used
+ assertThat(get(Example1.class, "first")).isInstanceOf(FieldProperty.class);
+ assertThat(get(Example5.class, "first")).isInstanceOf(FieldProperty.class);
+ }
+
+ @Test
+ public void fieldPresentWithGetter()
+ {
+ // if setter/getter present, methods take precendents over field
+ assertThat(get(Example2.class, "first")).isInstanceOf(MethodProperty.class);
+ }
+
+ @Test
+ public void fieldPresentWithoutSetter()
+ {
+ assertThat(get(Example3.class, "first")).isInstanceOf(FieldProperty.class);
+ assertThat(get(Example4.class, "first")).isInstanceOf(FieldProperty.class);
+ }
+
+ @Test
+ public void noFieldWithGetterAndSetter()
+ {
+ assertThat(get(Example6.class, "first")).isInstanceOf(MethodProperty.class);
+ }
+
+ @Test
+ public void noFieldWithGetterAndNoSetter()
+ {
+ assertThat(get(Example7.class, "first")).isNull();
+ assertThat(get(Example8.class, "first")).isNull();
+ }
+
+ @Test
+ public void noFieldWithoutGetterAndWithSetter()
+ {
+ assertThat(get(Example9.class, "first")).isInstanceOf(MethodProperty.class);
+ assertThat(get(Example10.class, "first")).isInstanceOf(MethodProperty.class);
+ }
+
+ private static Property get(Class<?> klass, String name)
+ {
+ return LOADER.getProperties(klass).get(name);
+ }
+
+ public static class Example0
+ {
+ public int first;
+ }
+
+ public static class Example1
+ {
+ public int first;
+
+ public void setFirst(int first)
+ {
+ this.first = first;
+ }
+ }
+
+ public static class Example2
+ {
+ public int first;
+
+ public void setFirst(int first)
+ {
+ this.first = first;
+ }
+
+ public int getFirst()
+ {
+ return first;
+ }
+ }
+
+ public static class Example3
+ {
+ public int first;
+
+ public int getFirst()
+ {
+ return first;
+ }
+ }
+
+ public static class Example4
+ {
+ public int first;
+
+ @JsonIgnore
+ public void setFirst(int first)
+ {
+ this.first = first;
+ }
+
+ public int getFirst()
+ {
+ return first;
+ }
+ }
+
+ public static class Example5
+ {
+ public int first;
+
+ public void setFirst(int first)
+ {
+ this.first = first;
+ }
+
+ @JsonIgnore
+ public int getFirst()
+ {
+ return first;
+ }
+ }
+
+ public static class Example6
+ {
+ public int getFirst()
+ {
+ return 42;
+ }
+
+ public void setFirst(int first)
+ {
+ // no-op
+ }
+ }
+
+ public static class Example7
+ {
+ public int getFirst()
+ {
+ return 42;
+ }
+ }
+
+ public static class Example8
+ {
+ public int getFirst()
+ {
+ return 42;
+ }
+
+ @JsonIgnore
+ public void setFirst(int first)
+ {
+ // no-op
+ }
+ }
+
+ public static class Example9
+ {
+ public void setFirst(int first)
+ {
+ // no-op
+ }
+ }
+
+ public static class Example10
+ {
+ @JsonIgnore
+ public int getFirst()
+ {
+ return 42;
+ }
+
+ public void setFirst(int first)
+ {
+ // no-op
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/config/LoaderTest.java b/test/unit/org/apache/cassandra/config/LoaderTest.java
new file mode 100644
index 0000000000..5637e9836e
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/LoaderTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.junit.Test;
+
+import org.assertj.core.api.Assertions;
+import org.yaml.snakeyaml.introspector.Property;
+
+public class LoaderTest
+{
+ /**
+ * This test mostly validates that DefaultLoader will match what SnakeYAML would do (if camel casing gets converted to snake),
+ * if this test fails it is likely a new property was added and not properly picked up by one of the two implementations;
+ * if you added a property and now this test is failing, make sure to follow all of the following rules:
+ *
+ * 1) public field
+ * 2) getter AND setter
+ * 3) if getter of Boolean, use "get" prefix rather than "is"
+ */
+ @Test
+ public void allMatchProperties()
+ {
+ Class<?> klass = Config.class;
+ // treeset makes error cleaner to read
+ Set<String> properties = defaultImpl(klass).keySet();
+ Assertions.assertThat(new TreeSet<>(properties))
+ .isEqualTo(new TreeSet<>(snake(klass).keySet()));
+ }
+
+ private static Map<String, Property> snake(Class<?> klass)
+ {
+ return new SnakeYamlLoader().flatten(klass);
+ }
+
+ private static Map<String, Property> defaultImpl(Class<?> klass)
+ {
+ return new DefaultLoader().flatten(klass);
+ }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/config/PropertiesTest.java b/test/unit/org/apache/cassandra/config/PropertiesTest.java
new file mode 100644
index 0000000000..f1e9322f3b
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/PropertiesTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.Map;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.junit.Test;
+
+import org.yaml.snakeyaml.introspector.Property;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PropertiesTest
+{
+ private final Loader loader = Properties.defaultLoader();
+
+ @Test
+ public void backAndForth() throws Exception
+ {
+ Map<String, Property> ps = loader.flatten(Config.class);
+
+ Config config = new Config();
+ Set<String> keys = ImmutableSet.of("server_encryption_options.enabled",
+ "client_encryption_options.enabled",
+ "server_encryption_options.optional",
+ "client_encryption_options.optional");
+ for (Property prop : ps.values())
+ {
+ // skip these properties as they don't allow get/set within the context of this test
+ if (keys.contains(prop.getName()))
+ continue;
+ Object value = prop.get(config);
+ if (value == null)
+ continue;
+ prop.set(config, value);
+ Object back = prop.get(config);
+ assertThat(back).isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void configMutate() throws Exception
+ {
+ Map<String, Property> ps = loader.getProperties(Config.class);
+ assertThat(ps)
+ .isNotEmpty()
+ .containsKey("cluster_name") // string / primitive
+ .containsKey("permissions_validity") // SmallestDurationSeconds
+ .containsKey("hinted_handoff_disabled_datacenters") // set<string>
+ .containsKey("disk_access_mode"); // enum
+
+ // can we mutate the config?
+ Config config = new Config();
+
+ ps.get("cluster_name").set(config, "properties testing");
+ assertThat(config.cluster_name).isEqualTo("properties testing");
+
+ ps.get("permissions_validity").set(config, SmallestDurationMilliseconds.inMilliseconds(42));
+ assertThat(config.permissions_validity.toMilliseconds()).isEqualTo(42);
+
+ ps.get("hinted_handoff_disabled_datacenters").set(config, Sets.newHashSet("a", "b", "c"));
+ assertThat(config.hinted_handoff_disabled_datacenters).isEqualTo(Sets.newHashSet("a", "b", "c"));
+
+ ps.get("disk_access_mode").set(config, Config.DiskAccessMode.mmap);
+ assertThat(config.disk_access_mode).isEqualTo(Config.DiskAccessMode.mmap);
+ }
+
+ @Test
+ public void nestedMutate() throws Exception
+ {
+ Map<String, Property> ps = loader.flatten(Config.class);
+
+ assertThat(ps)
+ .containsKey("seed_provider.class_name");
+
+ // can we mutate the config?
+ Config config = new Config();
+
+ ps.get("seed_provider.class_name").set(config, "testing");
+ assertThat(config.seed_provider.class_name).isEqualTo("testing");
+ }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/config/SnakeYamlLoader.java b/test/unit/org/apache/cassandra/config/SnakeYamlLoader.java
new file mode 100644
index 0000000000..90c9a1cc5f
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/SnakeYamlLoader.java
@@ -0,0 +1,90 @@
+/*
+ * 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.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.introspector.BeanAccess;
+import org.yaml.snakeyaml.introspector.Property;
+import org.yaml.snakeyaml.introspector.PropertyUtils;
+
+import static org.apache.cassandra.utils.FBUtilities.camelToSnake;
+
+public final class SnakeYamlLoader implements Loader
+{
+ private final Helper helper = new Helper();
+
+ @Override
+ public Map<String, Property> getProperties(Class<?> root)
+ {
+ return helper.getPropertiesMap(root, BeanAccess.DEFAULT);
+ }
+
+ private static class Helper extends PropertyUtils
+ {
+ @Override
+ public Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess)
+ {
+ Map<String, Property> map;
+ try
+ {
+ map = super.getPropertiesMap(type, bAccess);
+ }
+ catch (YAMLException e)
+ {
+ // some classes take a string in constructor and output as toString, these should be treated like
+ // primitive types
+ if (e.getMessage() != null && e.getMessage().startsWith("No JavaBean properties found"))
+ return Collections.emptyMap();
+ throw e;
+ }
+ // filter out ignores
+ Set<String> ignore = new HashSet<>();
+ Map<String, String> rename = new HashMap<>();
+ map.values().forEach(p -> {
+ if (shouldIgnore(p))
+ {
+ ignore.add(p.getName());
+ return;
+ }
+ String snake = camelToSnake(p.getName());
+ if (!p.getName().equals(snake))
+ {
+ if (map.containsKey(snake))
+ ignore.add(p.getName());
+ else
+ rename.put(p.getName(), snake);
+ }
+ });
+ ignore.forEach(map::remove);
+ rename.forEach((previous, desired) -> map.put(desired, map.remove(previous)));
+ return map;
+ }
+
+ private static boolean shouldIgnore(Property p)
+ {
+ return !p.isWritable() || p.getAnnotation(JsonIgnore.class) != null;
+ }
+ }
+}
diff --git a/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java b/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java
index 321315313b..aaf60e1ca7 100644
--- a/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java
+++ b/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java
@@ -28,8 +28,11 @@ import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.io.util.File;
+import static org.apache.cassandra.config.CassandraRelevantProperties.CONFIG_ALLOW_SYSTEM_PROPERTIES;
+import static org.apache.cassandra.config.YamlConfigurationLoader.SYSTEM_PROPERTY_PREFIX;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
@@ -37,6 +40,57 @@ import static org.junit.Assert.assertEquals;
public class YamlConfigurationLoaderTest
{
+ @Test
+ public void updateInPlace()
+ {
+ Config config = new Config();
+ Map<String, Object> map = ImmutableMap.<String, Object>builder().put("storage_port", 123)
+ .put("commitlog_sync", Config.CommitLogSync.batch)
+ .put("seed_provider.class_name", "org.apache.cassandra.locator.SimpleSeedProvider")
+ .put("client_encryption_options.cipher_suites", Collections.singletonList("FakeCipher"))
+ .put("client_encryption_options.optional", false)
+ .put("client_encryption_options.enabled", true)
+ .build();
+ Config updated = YamlConfigurationLoader.updateFromMap(map, true, config);
+ assert updated == config : "Config pointers do not match";
+ assertThat(config.storage_port).isEqualTo(123);
+ assertThat(config.commitlog_sync).isEqualTo(Config.CommitLogSync.batch);
+ assertThat(config.seed_provider.class_name).isEqualTo("org.apache.cassandra.locator.SimpleSeedProvider");
+ assertThat(config.client_encryption_options.cipher_suites).isEqualTo(Collections.singletonList("FakeCipher"));
+ assertThat(config.client_encryption_options.optional).isFalse();
+ assertThat(config.client_encryption_options.enabled).isTrue();
+ }
+
+ @Test
+ public void withSystemProperties()
+ {
+ // for primitive types or data-types which use a String constructor, we can support these as nested
+ // if the type is a collection, then the string format doesn't make sense and will fail with an error such as
+ // Cannot create property=client_encryption_options.cipher_suites for JavaBean=org.apache.cassandra.config.Config@1f59a598
+ // No single argument constructor found for interface java.util.List : null
+ // the reason is that its not a scalar but a complex type (collection type), so the map we use needs to have a collection to match.
+ // It is possible that we define a common string representation for these types so they can be written to; this
+ // is an issue that SettingsTable may need to worry about.
+ try (WithProperties ignore = new WithProperties(CONFIG_ALLOW_SYSTEM_PROPERTIES.getKey(), "true",
+ SYSTEM_PROPERTY_PREFIX + "storage_port", "123",
+ SYSTEM_PROPERTY_PREFIX + "commitlog_sync", "batch",
+ SYSTEM_PROPERTY_PREFIX + "seed_provider.class_name", "org.apache.cassandra.locator.SimpleSeedProvider",
+// PROPERTY_PREFIX + "client_encryption_options.cipher_suites", "[\"FakeCipher\"]",
+ SYSTEM_PROPERTY_PREFIX + "client_encryption_options.optional", "false",
+ SYSTEM_PROPERTY_PREFIX + "client_encryption_options.enabled", "true",
+ SYSTEM_PROPERTY_PREFIX + "doesnotexist", "true"
+ ))
+ {
+ Config config = YamlConfigurationLoader.fromMap(Collections.emptyMap(), true, Config.class);
+ assertThat(config.storage_port).isEqualTo(123);
+ assertThat(config.commitlog_sync).isEqualTo(Config.CommitLogSync.batch);
+ assertThat(config.seed_provider.class_name).isEqualTo("org.apache.cassandra.locator.SimpleSeedProvider");
+// assertThat(config.client_encryption_options.cipher_suites).isEqualTo(Collections.singletonList("FakeCipher"));
+ assertThat(config.client_encryption_options.optional).isFalse();
+ assertThat(config.client_encryption_options.enabled).isTrue();
+ }
+ }
+
@Test
public void readThresholdsFromConfig()
{
diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
index 8239cefd95..98b29b520a 100644
--- a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
+++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
@@ -18,10 +18,8 @@
package org.apache.cassandra.db.virtual;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
@@ -37,6 +35,7 @@ import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.Int
import org.apache.cassandra.config.ParameterizedClass;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.security.SSLFactory;
+import org.yaml.snakeyaml.introspector.Property;
public class SettingsTableTest extends CQLTester
{
@@ -62,22 +61,6 @@ public class SettingsTableTest extends CQLTester
disablePreparedReuseForTest();
}
- private String getValue(Field f)
- {
- Object untypedValue = table.getValue(f);
- String value = null;
- if (untypedValue != null)
- {
- if (untypedValue.getClass().isArray())
- {
- value = Arrays.toString((Object[]) untypedValue);
- }
- else
- value = untypedValue.toString();
- }
- return value;
- }
-
@Test
public void testSelectAll() throws Throwable
{
@@ -88,26 +71,22 @@ public class SettingsTableTest extends CQLTester
{
i++;
String name = r.getString("name");
- Field f = SettingsTable.FIELDS.get(name);
- if (f != null) // skip overrides
- Assert.assertEquals(getValue(f), r.getString("value"));
+ Property prop = SettingsTable.PROPERTIES.get(name);
+ if (prop != null) // skip overrides
+ Assert.assertEquals(getValue(prop), r.getString("value"));
}
- Assert.assertTrue(SettingsTable.FIELDS.size() <= i);
+ Assert.assertTrue(SettingsTable.PROPERTIES.size() <= i);
}
@Test
public void testSelectPartition() throws Throwable
{
- List<Field> fields = Arrays.stream(Config.class.getFields())
- .filter(f -> !Modifier.isStatic(f.getModifiers()))
- .collect(Collectors.toList());
- for (Field f : fields)
+ for (Map.Entry<String, Property> e : SettingsTable.PROPERTIES.entrySet())
{
- if (table.overrides.containsKey(f.getName()))
- continue;
-
- String q = "SELECT * FROM vts.settings WHERE name = '"+f.getName()+'\'';
- assertRowsNet(executeNet(q), new Object[] {f.getName(), getValue(f)});
+ String name = e.getKey();
+ Property prop = e.getValue();
+ String q = "SELECT * FROM vts.settings WHERE name = '"+name+'\'';
+ assertRowsNet(executeNet(q), new Object[] { name, getValue(prop) });
}
}
@@ -127,10 +106,25 @@ public class SettingsTableTest extends CQLTester
assertRowsNet(executeNet(q));
}
+ private String getValue(Property prop)
+ {
+ Object v = prop.get(config);
+ if (v != null)
+ return v.toString();
+ return null;
+ }
+
private void check(String setting, String expected) throws Throwable
{
String q = "SELECT * FROM vts.settings WHERE name = '"+setting+'\'';
- assertRowsNet(executeNet(q), new Object[] {setting, expected});
+ try
+ {
+ assertRowsNet(executeNet(q), new Object[] {setting, expected});
+ }
+ catch (AssertionError e)
+ {
+ throw new AssertionError(e.getMessage() + " for query " + q);
+ }
}
@Test
@@ -141,7 +135,8 @@ public class SettingsTableTest extends CQLTester
String all = "SELECT * FROM vts.settings WHERE " +
"name > 'server_encryption' AND name < 'server_encryptionz' ALLOW FILTERING";
- Assert.assertEquals(9, executeNet(all).all().size());
+ List<String> expectedNames = SettingsTable.PROPERTIES.keySet().stream().filter(n -> n.startsWith("server_encryption")).collect(Collectors.toList());
+ Assert.assertEquals(expectedNames.size(), executeNet(all).all().size());
check(pre + "algorithm", null);
config.server_encryption_options = config.server_encryption_options.withAlgorithm("SUPERSSL");
@@ -151,6 +146,7 @@ public class SettingsTableTest extends CQLTester
config.server_encryption_options = config.server_encryption_options.withCipherSuites("c1", "c2");
check(pre + "cipher_suites", "[c1, c2]");
+ // name doesn't match yaml
check(pre + "protocol", null);
config.server_encryption_options = config.server_encryption_options.withProtocol("TLSv5");
check(pre + "protocol", "[TLSv5]");
@@ -170,10 +166,12 @@ public class SettingsTableTest extends CQLTester
config.server_encryption_options = config.server_encryption_options.withOptional(true);
check(pre + "optional", "true");
+ // name doesn't match yaml
check(pre + "client_auth", "false");
config.server_encryption_options = config.server_encryption_options.withRequireClientAuth(true);
check(pre + "client_auth", "true");
+ // name doesn't match yaml
check(pre + "endpoint_verification", "false");
config.server_encryption_options = config.server_encryption_options.withRequireEndpointVerification(true);
check(pre + "endpoint_verification", "true");
@@ -183,6 +181,7 @@ public class SettingsTableTest extends CQLTester
check(pre + "internode_encryption", "all");
check(pre + "enabled", "true");
+ // name doesn't match yaml
check(pre + "legacy_ssl_storage_port", "false");
config.server_encryption_options = config.server_encryption_options.withLegacySslStoragePort(true);
check(pre + "legacy_ssl_storage_port", "true");
@@ -197,9 +196,11 @@ public class SettingsTableTest extends CQLTester
"name > 'audit_logging' AND name < 'audit_loggingz' ALLOW FILTERING";
config.audit_logging_options.enabled = true;
- Assert.assertEquals(9, executeNet(all).all().size());
+ List<String> expectedNames = SettingsTable.PROPERTIES.keySet().stream().filter(n -> n.startsWith("audit_logging")).collect(Collectors.toList());
+ Assert.assertEquals(expectedNames.size(), executeNet(all).all().size());
check(pre + "enabled", "true");
+ // name doesn't match yaml
check(pre + "logger", "BinAuditLogger");
config.audit_logging_options.logger = new ParameterizedClass("logger", null);
check(pre + "logger", "logger");
@@ -242,7 +243,8 @@ public class SettingsTableTest extends CQLTester
"name < 'transparent_data_encryption_optionsz' ALLOW FILTERING";
config.transparent_data_encryption_options.enabled = true;
- Assert.assertEquals(4, executeNet(all).all().size());
+ List<String> expectedNames = SettingsTable.PROPERTIES.keySet().stream().filter(n -> n.startsWith("transparent_data_encryption_options")).collect(Collectors.toList());
+ Assert.assertEquals(expectedNames.size(), executeNet(all).all().size());
check(pre + "enabled", "true");
check(pre + "cipher", "AES/CBC/PKCS5Padding");
diff --git a/test/unit/org/apache/cassandra/net/MessagingServiceTest.java b/test/unit/org/apache/cassandra/net/MessagingServiceTest.java
index 733584b149..349d8652cf 100644
--- a/test/unit/org/apache/cassandra/net/MessagingServiceTest.java
+++ b/test/unit/org/apache/cassandra/net/MessagingServiceTest.java
@@ -403,12 +403,12 @@ public class MessagingServiceTest
final int legacySslPort = DatabaseDescriptor.getSSLStoragePort();
for (InboundSockets.InboundSocket socket : connections.sockets())
{
- Assert.assertEquals(serverEncryptionOptions.isEnabled(), socket.settings.encryption.isEnabled());
- Assert.assertEquals(serverEncryptionOptions.isOptional(), socket.settings.encryption.isOptional());
- if (!serverEncryptionOptions.isEnabled())
+ Assert.assertEquals(serverEncryptionOptions.getEnabled(), socket.settings.encryption.getEnabled());
+ Assert.assertEquals(serverEncryptionOptions.getOptional(), socket.settings.encryption.getOptional());
+ if (!serverEncryptionOptions.getEnabled())
assertNotEquals(legacySslPort, socket.settings.bindAddress.getPort());
if (legacySslPort == socket.settings.bindAddress.getPort())
- Assert.assertFalse(socket.settings.encryption.isOptional());
+ Assert.assertFalse(socket.settings.encryption.getOptional());
Assert.assertTrue(socket.settings.bindAddress.toString(), expect.remove(socket.settings.bindAddress));
}
}
diff --git a/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java b/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java
index 833f2e41bf..5294d81073 100644
--- a/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java
+++ b/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java
@@ -23,6 +23,7 @@ import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
@@ -45,7 +46,9 @@ import org.apache.cassandra.dht.*;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.assertj.core.api.Assertions;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -227,4 +230,29 @@ public class FBUtilitiesTest
executor.shutdown();
}
}
+
+ @Test
+ public void testCamelToSnake()
+ {
+ AssertionError error = null;
+ for (Pair<String, String> a : Arrays.asList(Pair.create("Testing", "testing"),
+ Pair.create("fooBarBaz", "foo_bar_baz"),
+ Pair.create("foo_bar_baz", "foo_bar_baz")
+ ))
+ {
+ try
+ {
+ assertThat(FBUtilities.camelToSnake(a.left)).isEqualTo(a.right);
+ }
+ catch (AssertionError e)
+ {
+ if (error == null)
+ error = e;
+ else
+ error.addSuppressed(e);
+ }
+ }
+ if (error != null)
+ throw error;
+ }
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
index e0b734ed2e..37be3b4857 100644
--- a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
+++ b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
@@ -145,7 +145,7 @@ public class JavaDriverClient
if (loadBalancingPolicy != null)
clusterBuilder.withLoadBalancingPolicy(loadBalancingPolicy);
clusterBuilder.withCompression(compression);
- if (encryptionOptions.isEnabled())
+ if (encryptionOptions.getEnabled())
{
SSLContext sslContext;
sslContext = SSLFactory.createSSLContext(encryptionOptions, true);
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org