diff --git a/CHANGES.txt b/CHANGES.txt index 93cc0ead34..273ccebbae 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.0.6 + * Represent complex settings as JSON on system_views.settings table (CASSANDRA-20827) * Expose StorageService.dropPreparedStatements via JMX (CASSANDRA-20870) * Sort SSTable TOC entries for determinism (CASSANDRA-20494) Merged from 4.1: diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java index e1e080a0a4..f8b983ba74 100644 --- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java +++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java @@ -607,6 +607,8 @@ public enum CassandraRelevantProperties USE_NIX_RECURSIVE_DELETE("cassandra.use_nix_recursive_delete"), /** Gossiper compute expiration timeout. Default value 3 days. */ VERY_LONG_TIME_MS("cassandra.very_long_time_ms", "259200000"), + /** Controls output format for Collection-type settings in system_views.settings table */ + VIRTUAL_TABLE_COMPLEX_SETTINGS_FORMAT_JSON("cassandra.virtual_table_complex_settings_format_json", "false"), WAIT_FOR_TRACING_EVENTS_TIMEOUT_SECS("cassandra.wait_for_tracing_events_timeout_secs", "0"); static diff --git a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java index c4e7d95698..379b6793d8 100644 --- a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java +++ b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java @@ -18,6 +18,7 @@ package org.apache.cassandra.db.virtual; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -25,6 +26,7 @@ import java.util.Objects; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.Redacted; import org.apache.cassandra.config.DatabaseDescriptor; @@ -36,6 +38,7 @@ 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.utils.JsonUtils; import org.apache.cassandra.service.ClientWarn; import org.yaml.snakeyaml.introspector.Property; @@ -48,6 +51,7 @@ final class SettingsTable extends AbstractVirtualTable protected static final Map PROPERTIES = ImmutableMap.copyOf(getProperties()); private final Config config; + private final boolean useJsonFormat; SettingsTable(String keyspace) { @@ -64,6 +68,7 @@ final class SettingsTable extends AbstractVirtualTable .addRegularColumn(VALUE, UTF8Type.instance) .build()); this.config = config; + this.useJsonFormat = CassandraRelevantProperties.VIRTUAL_TABLE_COMPLEX_SETTINGS_FORMAT_JSON.getBoolean(); } @Override @@ -99,9 +104,10 @@ final class SettingsTable extends AbstractVirtualTable return null; if (value.getClass().isArray()) - return Arrays.asList((Object[]) value).toString(); - - if (value instanceof Map) + return tryConstructJson(Arrays.asList((Object[]) value)); + else if (value instanceof Collection) + return tryConstructJson(value); + else if (value instanceof Map) { Map map = new HashMap<>(); for (Map.Entry entry : ((Map) value).entrySet()) @@ -115,12 +121,31 @@ final class SettingsTable extends AbstractVirtualTable map.put(entry.getKey(), entry.getValue()); } - return map.toString(); + return tryConstructJson(map); } return value.toString(); } + private String tryConstructJson(Object o) + { + if (useJsonFormat) + { + try + { + return JsonUtils.JSON_OBJECT_MAPPER.writeValueAsString(o); + } + catch (Exception e) + { + return o.toString(); + } + } + else + { + return o.toString(); + } + } + private static Map getProperties() { Loader loader = Properties.defaultLoader(); diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java index 1470fac238..82d4133422 100644 --- a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java +++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java @@ -18,6 +18,7 @@ package org.apache.cassandra.db.virtual; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -33,6 +34,7 @@ import org.junit.Test; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.Redacted; import org.apache.cassandra.config.DefaultLoader; @@ -41,13 +43,17 @@ import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.Int import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.config.TransparentDataEncryptionOptions; import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.distributed.shared.WithProperties; import org.apache.cassandra.security.SSLFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.introspector.Property; import static java.util.stream.Collectors.toMap; public class SettingsTableTest extends CQLTester { + private static final Logger logger = LoggerFactory.getLogger(SettingsTableTest.class); private static final String KS_NAME = "vts"; private Config config; @@ -172,12 +178,12 @@ public class SettingsTableTest extends CQLTester assertRowsNet(executeNet(q), new Object[] {"credentials_update_interval_in_ms", "-1"}); } - private void check(String setting, String expected) throws Throwable + private void check(String keyspaceTable, String setting, String expected) { - String q = "SELECT * FROM vts.settings WHERE name = '"+setting+'\''; + String q = "SELECT * FROM " + keyspaceTable + " WHERE name = '" + setting + '\''; try { - assertRowsNet(executeNet(q), new Object[] {setting, expected}); + assertRowsNet(executeNet(q), new Object[]{ setting, expected }); } catch (AssertionError e) { @@ -185,6 +191,11 @@ public class SettingsTableTest extends CQLTester } } + private void check(String setting, String expected) + { + check("vts.settings", setting, expected); + } + @Test public void testEncryptionOverride() throws Throwable { @@ -353,4 +364,38 @@ public class SettingsTableTest extends CQLTester Assert.assertEquals(settingName, name); Assert.assertEquals(expectedValue, value); } + + @Test + public void testComplexSettingsFormatProperty() + { + Map parameters = new HashMap<>(); + parameters.put("seeds", "127.0.0.1:7000"); + config.seed_provider = new ParameterizedClass("org.apache.cassandra.locator.SimpleSeedProvider", parameters); + + // Test set property to true (collection as json) + try (WithProperties properties = new WithProperties().set(CassandraRelevantProperties.VIRTUAL_TABLE_COMPLEX_SETTINGS_FORMAT_JSON, "true")) + { + table = new SettingsTable("json_true", config); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace("json_true", ImmutableList.of(table))); + + check("json_true.settings", "data_file_directories", "[\"/my/data/directory\",\"/another/data/directory\"]"); + check("json_true.settings", "seed_provider.parameters", "{\"seeds\":\"127.0.0.1:7000\"}"); + } + } + + @Test + public void testOldBehaviourForComplexSettingsFormatProperty() + { + Map parameters = new HashMap<>(); + parameters.put("seeds", "127.0.0.1:7000"); + config.seed_provider = new ParameterizedClass("org.apache.cassandra.locator.SimpleSeedProvider", parameters); + + // we are not setting property here to false, we expect it to be false by default + + table = new SettingsTable("json_false", config); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace("json_false", ImmutableList.of(table))); + + check("json_false.settings", "data_file_directories", "[/my/data/directory, /another/data/directory]"); + check("json_false.settings", "seed_provider.parameters", "{seeds=127.0.0.1:7000}"); + } }