Repository: cassandra Updated Branches: refs/heads/trunk 6d1446ff0 -> e7e0e0b23
Add a virtual table to expose settings (CASSANDRA-14573) patch by Chris Lohfink; reviewed by Aleksey Yeschenko for CASSANDRA-14573 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/e7e0e0b2 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/e7e0e0b2 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/e7e0e0b2 Branch: refs/heads/trunk Commit: e7e0e0b233acf3584147435d0989a9e2474c09e4 Parents: 6d1446f Author: Chris Lohfink <clohf...@apple.com> Authored: Tue Aug 14 14:05:12 2018 -0500 Committer: Aleksey Yeshchenko <alek...@apple.com> Committed: Tue Aug 21 15:57:40 2018 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cassandra/db/virtual/SettingsTable.java | 189 ++++++++++++++ .../db/virtual/SystemViewsKeyspace.java | 1 + .../cassandra/db/virtual/SettingsTableTest.java | 245 +++++++++++++++++++ 4 files changed, 436 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/e7e0e0b2/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index aeaf8ce..9fbaf25 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0 + * Add a virtual table to expose settings (CASSANDRA-14573) * Fix up chunk cache handling of metrics (CASSANDRA-14628) * Extend IAuthenticator to accept peer SSL certificates (CASSANDRA-14652) * Incomplete handling of exceptions when decoding incoming messages (CASSANDRA-14574) http://git-wip-us.apache.org/repos/asf/cassandra/blob/e7e0e0b2/src/java/org/apache/cassandra/db/virtual/SettingsTable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java new file mode 100644 index 0000000..34debc6 --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java @@ -0,0 +1,189 @@ +/* + * 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.db.virtual; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Map; +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.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; + +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 + 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 final Config config; + + SettingsTable(String keyspace) + { + this(keyspace, DatabaseDescriptor.getRawConfig()); + } + + SettingsTable(String keyspace, Config config) + { + super(TableMetadata.builder(keyspace, "settings") + .comment("current settings") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(NAME, UTF8Type.instance) + .addRegularColumn(VALUE, UTF8Type.instance) + .build()); + 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()); + } + } + + @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)); + } + return result; + } + + @Override + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + for (Field setting : FIELDS.values()) + addValue(result, setting); + return result; + } + + private void addAuditLoggingOptions(SimpleDataSet result, Field f) + { + 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); + 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); + } + + private void addEncryptionOptions(SimpleDataSet result, Field f) + { + Preconditions.checkArgument(EncryptionOptions.class.isAssignableFrom(f.getType())); + + EncryptionOptions value = (EncryptionOptions) getValue(f); + result.row(f.getName() + "_enabled").column(VALUE, Boolean.toString(value.enabled)); + result.row(f.getName() + "_algorithm").column(VALUE, value.algorithm); + result.row(f.getName() + "_protocol").column(VALUE, value.protocol); + result.row(f.getName() + "_cipher_suites").column(VALUE, Arrays.toString(value.cipher_suites)); + 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.optional)); + + if (value instanceof EncryptionOptions.ServerEncryptionOptions) + { + 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.enable_legacy_ssl_storage_port)); + } + } + + private void addTransparentEncryptionOptions(SimpleDataSet result, Field f) + { + 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)); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/e7e0e0b2/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java index 3505a30..f85991a 100644 --- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java +++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java @@ -29,6 +29,7 @@ public final class SystemViewsKeyspace extends VirtualKeyspace { super(NAME, ImmutableList.of(new CachesTable(NAME), new ClientsTable(NAME), + new SettingsTable(NAME), new SSTableTasksTable(NAME), new ThreadPoolsTable(NAME))); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/e7e0e0b2/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java new file mode 100644 index 0000000..927835f --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java @@ -0,0 +1,245 @@ +/* + * 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.db.virtual; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption; +import org.apache.cassandra.cql3.CQLTester; + +public class SettingsTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + + private Config config; + private SettingsTable table; + + @BeforeClass + public static void setUpClass() + { + CQLTester.setUpClass(); + } + + @Before + public void config() + { + config = new Config(); + table = new SettingsTable(KS_NAME, config); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + } + + 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 + { + int paging = (int) (Math.random() * 100 + 1); + ResultSet result = executeNetWithPaging("SELECT * FROM vts.settings", paging); + int i = 0; + for (Row r : result) + { + i++; + String name = r.getString("name"); + Field f = SettingsTable.FIELDS.get(name); + if (f != null) // skip overrides + Assert.assertEquals(getValue(f), r.getString("value")); + } + Assert.assertTrue(SettingsTable.FIELDS.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) + { + 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)}); + } + } + + @Test + public void testSelectEmpty() throws Throwable + { + String q = "SELECT * FROM vts.settings WHERE name = 'EMPTY'"; + assertRowsNet(executeNet(q)); + } + + @Test + public void testSelectOverride() throws Throwable + { + String q = "SELECT * FROM vts.settings WHERE name = 'server_encryption_options_enabled'"; + assertRowsNet(executeNet(q), new Object[] {"server_encryption_options_enabled", "false"}); + q = "SELECT * FROM vts.settings WHERE name = 'server_encryption_options_XYZ'"; + assertRowsNet(executeNet(q)); + } + + 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}); + } + + @Test + public void testEncryptionOverride() throws Throwable + { + String pre = "server_encryption_options_"; + check(pre + "enabled", "false"); + String all = "SELECT * FROM vts.settings WHERE " + + "name > 'server_encryption' AND name < 'server_encryptionz' ALLOW FILTERING"; + + config.server_encryption_options.enabled = true; + Assert.assertEquals(9, executeNet(all).all().size()); + check(pre + "enabled", "true"); + + check(pre + "algorithm", null); + config.server_encryption_options.algorithm = "SUPERSSL"; + check(pre + "algorithm", "SUPERSSL"); + + check(pre + "cipher_suites", "[]"); + config.server_encryption_options.cipher_suites = new String[]{"c1", "c2"}; + check(pre + "cipher_suites", "[c1, c2]"); + + check(pre + "protocol", config.server_encryption_options.protocol); + config.server_encryption_options.protocol = "TLSv5"; + check(pre + "protocol", "TLSv5"); + + check(pre + "optional", "false"); + config.server_encryption_options.optional = true; + check(pre + "optional", "true"); + + check(pre + "client_auth", "false"); + config.server_encryption_options.require_client_auth = true; + check(pre + "client_auth", "true"); + + check(pre + "endpoint_verification", "false"); + config.server_encryption_options.require_endpoint_verification = true; + check(pre + "endpoint_verification", "true"); + + check(pre + "internode_encryption", "none"); + config.server_encryption_options.internode_encryption = InternodeEncryption.all; + check(pre + "internode_encryption", "all"); + + check(pre + "legacy_ssl_storage_port", "false"); + config.server_encryption_options.enable_legacy_ssl_storage_port = true; + check(pre + "legacy_ssl_storage_port", "true"); + } + + @Test + public void testAuditOverride() throws Throwable + { + String pre = "audit_logging_options_"; + check(pre + "enabled", "false"); + String all = "SELECT * FROM vts.settings WHERE " + + "name > 'audit_logging' AND name < 'audit_loggingz' ALLOW FILTERING"; + + config.audit_logging_options.enabled = true; + Assert.assertEquals(9, executeNet(all).all().size()); + check(pre + "enabled", "true"); + + check(pre + "logger", "BinAuditLogger"); + config.audit_logging_options.logger = "logger"; + check(pre + "logger", "logger"); + + config.audit_logging_options.audit_logs_dir = "dir"; + check(pre + "audit_logs_dir", "dir"); + + check(pre + "included_keyspaces", ""); + config.audit_logging_options.included_keyspaces = "included_keyspaces"; + check(pre + "included_keyspaces", "included_keyspaces"); + + check(pre + "excluded_keyspaces", ""); + config.audit_logging_options.excluded_keyspaces = "excluded_keyspaces"; + check(pre + "excluded_keyspaces", "excluded_keyspaces"); + + check(pre + "included_categories", ""); + config.audit_logging_options.included_categories = "included_categories"; + check(pre + "included_categories", "included_categories"); + + check(pre + "excluded_categories", ""); + config.audit_logging_options.excluded_categories = "excluded_categories"; + check(pre + "excluded_categories", "excluded_categories"); + + check(pre + "included_users", ""); + config.audit_logging_options.included_users = "included_users"; + check(pre + "included_users", "included_users"); + + check(pre + "excluded_users", ""); + config.audit_logging_options.excluded_users = "excluded_users"; + check(pre + "excluded_users", "excluded_users"); + } + + @Test + public void testTransparentEncryptionOptionsOverride() throws Throwable + { + String pre = "transparent_data_encryption_options_"; + check(pre + "enabled", "false"); + String all = "SELECT * FROM vts.settings WHERE " + + "name > 'transparent_data_encryption_options' AND " + + "name < 'transparent_data_encryption_optionsz' ALLOW FILTERING"; + + config.transparent_data_encryption_options.enabled = true; + Assert.assertEquals(4, executeNet(all).all().size()); + check(pre + "enabled", "true"); + + check(pre + "cipher", "AES/CBC/PKCS5Padding"); + config.transparent_data_encryption_options.cipher = "cipher"; + check(pre + "cipher", "cipher"); + + check(pre + "chunk_length_kb", "64"); + config.transparent_data_encryption_options.chunk_length_kb = 5; + check(pre + "chunk_length_kb", "5"); + + check(pre + "iv_length", "16"); + config.transparent_data_encryption_options.iv_length = 7; + check(pre + "iv_length", "7"); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org