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

Reply via email to