diff --git a/CHANGES.txt b/CHANGES.txt index 168e5b2bed..b3e78b4dea 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.0 + * Make cassandra-stress able to read all credentials from a file (CASSANDRA-18544) * Add guardrail for partition size and deprecate compaction_large_partition_warning_threshold (CASSANDRA-18500) * Add HISTORY command for CQLSH (CASSANDRA-15046) * Fix sstable formats configuration (CASSANDRA-18441) diff --git a/NEWS.txt b/NEWS.txt index 5c3267e1e0..b209e5adb0 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -142,6 +142,8 @@ New features Superusers have it by default, whereas regular users don't have it by default. - Added support for using UDFs as masking functions attached to table columns on the schema. - Added `sstablepartitions` offline tool to find large partitions in sstables. + - `cassandra-stress` has a new option called '-jmx' which enables a user to pass username and password to JMX (CASSANDRA-18544) + - It is possible to read all credentials for `cassandra-stress` from a file via option `-credentials-file` (CASSANDRA-18544) Upgrading --------- diff --git a/doc/modules/cassandra/pages/managing/tools/cassandra_stress.adoc b/doc/modules/cassandra/pages/managing/tools/cassandra_stress.adoc index 7cf3548c48..e24367caca 100644 --- a/doc/modules/cassandra/pages/managing/tools/cassandra_stress.adoc +++ b/doc/modules/cassandra/pages/managing/tools/cassandra_stress.adoc @@ -71,6 +71,10 @@ Primary Options::: Graph recorded metrics -tokenrange:;; Token range settings + -jmx:;; + Username and password for JMX connection + -credentials-file :;; + Credentials file to specify for CQL, JMX and transport Suboptions::: Every command and primary option has its own collection of suboptions. These are too numerous to list here. For information on the suboptions diff --git a/tools/stress/src/org/apache/cassandra/stress/report/StressMetrics.java b/tools/stress/src/org/apache/cassandra/stress/report/StressMetrics.java index b50dfd23f0..85bfc32121 100644 --- a/tools/stress/src/org/apache/cassandra/stress/report/StressMetrics.java +++ b/tools/stress/src/org/apache/cassandra/stress/report/StressMetrics.java @@ -114,7 +114,8 @@ public class StressMetrics implements MeasurementSink try { gcStatsCollector = new JmxCollector(toJmxNodes(settings.node.resolveAllPermitted(settings)), - settings.port.jmxPort); + settings.port.jmxPort, + settings.jmx); } catch (Throwable t) { diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java b/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java index eba276e241..3d721f3cc7 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java @@ -37,8 +37,10 @@ public enum CliOption LOG("Where to log progress to, and the interval at which to do it", SettingsLog.helpPrinter()), TRANSPORT("Custom transport factories", SettingsTransport.helpPrinter()), PORT("The port to connect to cassandra nodes on", SettingsPort.helpPrinter()), + JMX("JMX credentials", SettingsJMX.helpPrinter()), GRAPH("-graph", "Graph recorded metrics", SettingsGraph.helpPrinter()), - TOKENRANGE("Token range settings", SettingsTokenRange.helpPrinter()) + TOKENRANGE("Token range settings", SettingsTokenRange.helpPrinter()), + CREDENTIALS_FILE("Credentials file for CQL, JMX and transport", SettingsCredentials.helpPrinter()); ; private static final Map LOOKUP; @@ -63,11 +65,12 @@ public enum CliOption public final String description; private final Runnable helpPrinter; - private CliOption(String description, Runnable helpPrinter) + CliOption(String description, Runnable helpPrinter) { this(null, description, helpPrinter); } - private CliOption(String extraName, String description, Runnable helpPrinter) + + CliOption(String extraName, String description, Runnable helpPrinter) { this.extraName = extraName; this.description = description; @@ -79,4 +82,8 @@ public enum CliOption helpPrinter.run(); } + public String toString() + { + return name().replaceAll("_", "-"); + } } diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCredentials.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCredentials.java new file mode 100644 index 0000000000..76513b75f9 --- /dev/null +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCredentials.java @@ -0,0 +1,129 @@ +/* + * 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.stress.settings; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Map; +import java.util.Properties; + +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.stress.util.ResultLogger; + +public class SettingsCredentials implements Serializable +{ + public static final String CQL_USERNAME_PROPERTY_KEY = "cql.username"; + public static final String CQL_PASSWORD_PROPERTY_KEY = "cql.password"; + public static final String JMX_USERNAME_PROPERTY_KEY = "jmx.username"; + public static final String JMX_PASSWORD_PROPERTY_KEY = "jmx.password"; + public static final String TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY = "transport.truststore.password"; + public static final String TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY = "transport.keystore.password"; + + private final String file; + + public final String cqlUsername; + public final String cqlPassword; + public final String jmxUsername; + public final String jmxPassword; + public final String transportTruststorePassword; + public final String transportKeystorePassword; + + public SettingsCredentials(String file) + { + this.file = file; + if (file == null) + { + cqlUsername = null; + cqlPassword = null; + jmxUsername = null; + jmxPassword = null; + transportTruststorePassword = null; + transportKeystorePassword = null; + return; + } + + try + { + Properties properties = new Properties(); + try (InputStream is = new FileInputStream(new File(file).toJavaIOFile())) + { + properties.load(is); + + cqlUsername = properties.getProperty(CQL_USERNAME_PROPERTY_KEY); + cqlPassword = properties.getProperty(CQL_PASSWORD_PROPERTY_KEY); + jmxUsername = properties.getProperty(JMX_USERNAME_PROPERTY_KEY); + jmxPassword = properties.getProperty(JMX_PASSWORD_PROPERTY_KEY); + transportTruststorePassword = properties.getProperty(TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY); + transportKeystorePassword = properties.getProperty(TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY); + } + } + catch (IOException ioe) + { + throw new RuntimeException(ioe); + } + } + + // CLI Utility Methods + public void printSettings(ResultLogger out) + { + out.printf(" File: %s%n", file == null ? "*not set*" : file); + out.printf(" CQL username: %s%n", cqlUsername == null ? "*not set*" : cqlUsername); + out.printf(" CQL password: %s%n", cqlPassword == null ? "*not set*" : "*suppressed*"); + out.printf(" JMX username: %s%n", jmxUsername == null ? "*not set*" : jmxUsername); + out.printf(" JMX password: %s%n", jmxPassword == null ? "*not set*" : "*suppressed*"); + out.printf(" Transport truststore password: %s%n", transportTruststorePassword == null ? "*not set*" : "*suppressed*"); + out.printf(" Transport keystore password: %s%n", transportKeystorePassword == null ? "*not set*" : "*suppressed*"); + } + + public static SettingsCredentials get(Map clArgs) + { + String[] params = clArgs.remove("-credentials-file"); + if (params == null) + return new SettingsCredentials(null); + + if (params.length != 1) + { + printHelp(); + System.out.println("Invalid -credentials-file option provided, see output for valid options"); + System.exit(1); + } + + return new SettingsCredentials(params[0]); + } + + public static void printHelp() + { + System.out.println("Usage: -credentials-file "); + System.out.printf("File is supposed to be a standard property file with '%s', '%s', '%s', '%s', '%s', and '%s' as keys. " + + "The values for these keys will be overriden by their command-line counterparts when specified.%n", + CQL_USERNAME_PROPERTY_KEY, + CQL_PASSWORD_PROPERTY_KEY, + JMX_USERNAME_PROPERTY_KEY, + JMX_PASSWORD_PROPERTY_KEY, + TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY, + TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY); + } + + public static Runnable helpPrinter() + { + return SettingsCredentials::printHelp; + } +} diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsJMX.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsJMX.java new file mode 100644 index 0000000000..af202bef43 --- /dev/null +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsJMX.java @@ -0,0 +1,98 @@ +/* + * 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.stress.settings; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.cassandra.stress.util.ResultLogger; + +import static java.lang.String.format; +import static org.apache.cassandra.stress.settings.SettingsCredentials.JMX_PASSWORD_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.JMX_USERNAME_PROPERTY_KEY; + +public class SettingsJMX implements Serializable +{ + public final String user; + public final String password; + + public SettingsJMX(Options options, SettingsCredentials credentials) + { + this.user = options.user.setByUser() ? options.user.value() : credentials.jmxUsername; + this.password = options.password.setByUser() ? options.password.value() : credentials.jmxPassword; + } + + // Option Declarations + + public static final class Options extends GroupedOptions + { + final OptionSimple user = new OptionSimple("user=", + ".*", + null, + format("Username for JMX connection, when specified, it will override the value in credentials file for key '%s'", JMX_USERNAME_PROPERTY_KEY), + false); + + final OptionSimple password = new OptionSimple("password=", + ".*", + null, + format("Password for JMX connection, when specified, it will override the value in credentials file for key '%s'", JMX_PASSWORD_PROPERTY_KEY), + false); + + @Override + public List options() + { + return Arrays.asList(user, password); + } + } + + // CLI Utility Methods + public void printSettings(ResultLogger out) + { + out.printf(" Username: %s%n", user); + out.printf(" Password: %s%n", (password == null ? "*not set*" : "*suppressed*")); + } + + public static SettingsJMX get(Map clArgs, SettingsCredentials credentials) + { + String[] params = clArgs.remove("-jmx"); + if (params == null) + return new SettingsJMX(new SettingsJMX.Options(), credentials); + + GroupedOptions options = GroupedOptions.select(params, new SettingsJMX.Options()); + if (options == null) + { + printHelp(); + System.out.println("Invalid -jmx options provided, see output for valid options"); + System.exit(1); + } + return new SettingsJMX((SettingsJMX.Options) options, credentials); + } + + public static void printHelp() + { + GroupedOptions.printOptions(System.out, "-jmx", new SettingsJMX.Options()); + } + + public static Runnable helpPrinter() + { + return SettingsJMX::printHelp; + } +} diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java index 7e53547e81..b009d04177 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsMode.java @@ -32,6 +32,10 @@ import com.datastax.driver.core.ProtocolOptions; import com.datastax.driver.core.ProtocolVersion; import org.apache.cassandra.stress.util.ResultLogger; +import static java.lang.String.format; +import static org.apache.cassandra.stress.settings.SettingsCredentials.CQL_PASSWORD_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.CQL_USERNAME_PROPERTY_KEY; + public class SettingsMode implements Serializable { @@ -51,7 +55,7 @@ public class SettingsMode implements Serializable private final String compression; - public SettingsMode(GroupedOptions options) + public SettingsMode(GroupedOptions options, SettingsCredentials credentials) { if (options instanceof Cql3Options) { @@ -63,8 +67,8 @@ public class SettingsMode implements Serializable api = ConnectionAPI.JAVA_DRIVER_NATIVE; style = opts.useUnPrepared.setByUser() ? ConnectionStyle.CQL : ConnectionStyle.CQL_PREPARED; compression = ProtocolOptions.Compression.valueOf(opts.useCompression.value().toUpperCase()).name(); - username = opts.user.value(); - password = opts.password.value(); + username = opts.user.setByUser() ? opts.user.value() : credentials.cqlUsername; + password = opts.password.setByUser() ? opts.password.value() : credentials.cqlPassword; maxPendingPerConnection = opts.maxPendingPerConnection.value().isEmpty() ? null : Integer.valueOf(opts.maxPendingPerConnection.value()); connectionsPerHost = opts.connectionsPerHost.value().isEmpty() ? null : Integer.valueOf(opts.connectionsPerHost.value()); authProviderClassname = opts.authProvider.value(); @@ -137,8 +141,12 @@ public class SettingsMode implements Serializable final OptionSimple useUnPrepared = new OptionSimple("unprepared", "", null, "force use of unprepared statements", false); final OptionSimple useCompression = new OptionSimple("compression=", "none|lz4|snappy", "none", "", false); final OptionSimple port = new OptionSimple("port=", "[0-9]+", "9046", "", false); - final OptionSimple user = new OptionSimple("user=", ".+", null, "username", false); - final OptionSimple password = new OptionSimple("password=", ".+", null, "password", false); + final OptionSimple user = new OptionSimple("user=", ".+", null, + format("CQL user, when specified, it will override the value in credentials file for key '%s'", CQL_USERNAME_PROPERTY_KEY), + false); + final OptionSimple password = new OptionSimple("password=", ".+", null, + format("CQL password, when specified, it will override the value in credentials file for key '%s'", CQL_PASSWORD_PROPERTY_KEY), + false); final OptionSimple authProvider = new OptionSimple("auth-provider=", ".*", null, "Fully qualified implementation of com.datastax.driver.core.AuthProvider", false); final OptionSimple maxPendingPerConnection = new OptionSimple("maxPending=", "[0-9]+", "128", "Maximum pending requests per connection", false); final OptionSimple connectionsPerHost = new OptionSimple("connectionsPerHost=", "[0-9]+", "8", "Number of connections per host", false); @@ -179,11 +187,9 @@ public class SettingsMode implements Serializable out.printf(" Max Pending Per Connection: %d%n", maxPendingPerConnection); out.printf(" Connections Per Host: %d%n", connectionsPerHost); out.printf(" Compression: %s%n", compression); - } - - public static SettingsMode get(Map clArgs) + public static SettingsMode get(Map clArgs, SettingsCredentials credentials) { String[] params = clArgs.remove("-mode"); if (params == null) @@ -192,7 +198,7 @@ public class SettingsMode implements Serializable opts.accept("cql3"); opts.accept("native"); opts.accept("prepared"); - return new SettingsMode(opts); + return new SettingsMode(opts, credentials); } GroupedOptions options = GroupedOptions.select(params, new Cql3NativeOptions(), new Cql3SimpleNativeOptions()); @@ -202,7 +208,7 @@ public class SettingsMode implements Serializable System.out.println("Invalid -mode options provided, see output for valid options"); System.exit(1); } - return new SettingsMode(options); + return new SettingsMode(options, credentials); } public static void printHelp() diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java index 5f22a7b7c2..cf62999822 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java @@ -29,13 +29,19 @@ import java.util.Map; import org.apache.cassandra.config.EncryptionOptions; import org.apache.cassandra.stress.util.ResultLogger; +import static java.lang.String.format; +import static org.apache.cassandra.stress.settings.SettingsCredentials.TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY; + public class SettingsTransport implements Serializable { private final TOptions options; + private final SettingsCredentials credentials; - public SettingsTransport(TOptions options) + public SettingsTransport(TOptions options, SettingsCredentials credentials) { this.options = options; + this.credentials = credentials; } public EncryptionOptions getEncryptionOptions() @@ -46,7 +52,7 @@ public class SettingsTransport implements Serializable encOptions = encOptions .withEnabled(true) .withTrustStore(options.trustStore.value()) - .withTrustStorePassword(options.trustStorePw.value()) + .withTrustStorePassword(options.trustStorePw.setByUser() ? options.trustStorePw.value() : credentials.transportTruststorePassword) .withAlgorithm(options.alg.value()) .withProtocol(options.protocol.value()) .withCipherSuites(options.ciphers.value().split(",")); @@ -54,14 +60,14 @@ public class SettingsTransport implements Serializable { encOptions = encOptions .withKeyStore(options.keyStore.value()) - .withKeyStorePassword(options.keyStorePw.value()); + .withKeyStorePassword(options.keyStorePw.setByUser() ? options.keyStorePw.value() : credentials.transportKeystorePassword); } else { // mandatory for SSLFactory.createSSLContext(), see CASSANDRA-9325 encOptions = encOptions .withKeyStore(encOptions.truststore) - .withKeyStorePassword(encOptions.truststore_password); + .withKeyStorePassword(encOptions.truststore_password != null ? encOptions.truststore_password : credentials.transportTruststorePassword); } } return encOptions; @@ -72,12 +78,18 @@ public class SettingsTransport implements Serializable static class TOptions extends GroupedOptions implements Serializable { final OptionSimple trustStore = new OptionSimple("truststore=", ".*", null, "SSL: full path to truststore", false); - final OptionSimple trustStorePw = new OptionSimple("truststore-password=", ".*", null, "SSL: truststore password", false); + final OptionSimple trustStorePw = new OptionSimple("truststore-password=", ".*", null, + format("SSL: truststore password, when specified, it will override the value in credentials file of key '%s'", + TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY), false); final OptionSimple keyStore = new OptionSimple("keystore=", ".*", null, "SSL: full path to keystore", false); - final OptionSimple keyStorePw = new OptionSimple("keystore-password=", ".*", null, "SSL: keystore password", false); + final OptionSimple keyStorePw = new OptionSimple("keystore-password=", ".*", null, + format("SSL: keystore password, when specified, it will override the value in credentials file for key '%s'", + TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY), false); final OptionSimple protocol = new OptionSimple("ssl-protocol=", ".*", "TLS", "SSL: connection protocol to use", false); final OptionSimple alg = new OptionSimple("ssl-alg=", ".*", null, "SSL: algorithm", false); - final OptionSimple ciphers = new OptionSimple("ssl-ciphers=", ".*", "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA", "SSL: comma delimited list of encryption suites to use", false); + final OptionSimple ciphers = new OptionSimple("ssl-ciphers=", ".*", + "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA", + "SSL: comma delimited list of encryption suites to use", false); @Override public List options() @@ -89,14 +101,26 @@ public class SettingsTransport implements Serializable // CLI Utility Methods public void printSettings(ResultLogger out) { - out.println(" " + options.getOptionAsString()); + String tPassword = options.trustStorePw.setByUser() ? options.trustStorePw.value() : credentials.transportTruststorePassword; + tPassword = tPassword != null ? "*suppressed*" : tPassword; + + String kPassword = options.keyStorePw.setByUser() ? options.keyStore.value() : credentials.transportKeystorePassword; + kPassword = kPassword != null ? "*suppressed*" : kPassword; + + out.printf(" Truststore: %s%n", options.trustStore.value()); + out.printf(" Truststore Password: %s%n", tPassword); + out.printf(" Keystore: %s%n", options.keyStore.value()); + out.printf(" Keystore Password: %s%n", kPassword); + out.printf(" SSL Protocol: %s%n", options.protocol.value()); + out.printf(" SSL Algorithm: %s%n", options.alg.value()); + out.printf(" SSL Ciphers: %s%n", options.ciphers.value()); } - public static SettingsTransport get(Map clArgs) + public static SettingsTransport get(Map clArgs, SettingsCredentials credentials) { String[] params = clArgs.remove("-transport"); if (params == null) - return new SettingsTransport(new TOptions()); + return new SettingsTransport(new TOptions(), credentials); GroupedOptions options = GroupedOptions.select(params, new TOptions()); if (options == null) @@ -105,7 +129,7 @@ public class SettingsTransport implements Serializable System.out.println("Invalid -transport options provided, see output for valid options"); System.exit(1); } - return new SettingsTransport((TOptions) options); + return new SettingsTransport((TOptions) options, credentials); } public static void printHelp() diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/StressSettings.java b/tools/stress/src/org/apache/cassandra/stress/settings/StressSettings.java index 2f76dfb0fd..9b831187f2 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/StressSettings.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/StressSettings.java @@ -38,11 +38,13 @@ public class StressSettings implements Serializable public final SettingsColumn columns; public final SettingsErrors errors; public final SettingsLog log; + public final SettingsCredentials credentials; public final SettingsMode mode; public final SettingsNode node; public final SettingsSchema schema; public final SettingsTransport transport; public final SettingsPort port; + public final SettingsJMX jmx; public final SettingsGraph graph; public final SettingsTokenRange tokenRange; @@ -53,11 +55,13 @@ public class StressSettings implements Serializable SettingsColumn columns, SettingsErrors errors, SettingsLog log, + SettingsCredentials credentials, SettingsMode mode, SettingsNode node, SettingsSchema schema, SettingsTransport transport, SettingsPort port, + SettingsJMX jmx, SettingsGraph graph, SettingsTokenRange tokenRange) { @@ -68,11 +72,13 @@ public class StressSettings implements Serializable this.columns = columns; this.errors = errors; this.log = log; + this.credentials = credentials; this.mode = mode; this.node = node; this.schema = schema; this.transport = transport; this.port = port; + this.jmx = jmx; this.graph = graph; this.tokenRange = tokenRange; } @@ -194,10 +200,12 @@ public class StressSettings implements Serializable SettingsColumn columns = SettingsColumn.get(clArgs); SettingsErrors errors = SettingsErrors.get(clArgs); SettingsLog log = SettingsLog.get(clArgs); - SettingsMode mode = SettingsMode.get(clArgs); + SettingsCredentials credentials = SettingsCredentials.get(clArgs); + SettingsMode mode = SettingsMode.get(clArgs, credentials); SettingsNode node = SettingsNode.get(clArgs); SettingsSchema schema = SettingsSchema.get(clArgs, command); - SettingsTransport transport = SettingsTransport.get(clArgs); + SettingsTransport transport = SettingsTransport.get(clArgs, credentials); + SettingsJMX jmx = SettingsJMX.get(clArgs, credentials); SettingsGraph graph = SettingsGraph.get(clArgs, command); if (!clArgs.isEmpty()) { @@ -216,7 +224,7 @@ public class StressSettings implements Serializable System.exit(1); } - return new StressSettings(command, rate, generate, insert, columns, errors, log, mode, node, schema, transport, port, graph, tokenRange); + return new StressSettings(command, rate, generate, insert, columns, errors, log, credentials, mode, node, schema, transport, port, jmx, graph, tokenRange); } private static Map parseMap(String[] args) @@ -290,10 +298,14 @@ public class StressSettings implements Serializable transport.printSettings(out); out.println("Port:"); port.printSettings(out); + out.println("JMX:"); + jmx.printSettings(out); out.println("Graph:"); graph.printSettings(out); out.println("TokenRange:"); tokenRange.printSettings(out); + out.println("Credentials file:"); + credentials.printSettings(out); if (command.type == Command.USER) { diff --git a/tools/stress/src/org/apache/cassandra/stress/util/JmxCollector.java b/tools/stress/src/org/apache/cassandra/stress/util/JmxCollector.java index 8cfbebbd65..24cb4c788b 100644 --- a/tools/stress/src/org/apache/cassandra/stress/util/JmxCollector.java +++ b/tools/stress/src/org/apache/cassandra/stress/util/JmxCollector.java @@ -28,6 +28,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.stress.settings.SettingsJMX; import org.apache.cassandra.tools.NodeProbe; public class JmxCollector implements Callable @@ -76,23 +77,26 @@ public class JmxCollector implements Callable final NodeProbe[] probes; // TODO: should expand to whole cluster - public JmxCollector(Collection hosts, int port) + public JmxCollector(Collection hosts, int port, SettingsJMX jmx) { probes = new NodeProbe[hosts.size()]; int i = 0; for (String host : hosts) { - probes[i] = connect(host, port); + probes[i] = connect(host, port, jmx); probes[i].getAndResetGCStats(); i++; } } - private static NodeProbe connect(String host, int port) + private static NodeProbe connect(String host, int port, SettingsJMX jmx) { try { - return new NodeProbe(host, port); + if (jmx.user != null && jmx.password != null) + return new NodeProbe(host, port, jmx.user, jmx.password); + else + return new NodeProbe(host, port); } catch (IOException e) { diff --git a/tools/stress/test/unit/org/apache/cassandra/stress/settings/SettingsCredentialsTest.java b/tools/stress/test/unit/org/apache/cassandra/stress/settings/SettingsCredentialsTest.java new file mode 100644 index 0000000000..a1a07c4612 --- /dev/null +++ b/tools/stress/test/unit/org/apache/cassandra/stress/settings/SettingsCredentialsTest.java @@ -0,0 +1,126 @@ +/* + * 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.stress.settings; + +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.junit.Test; + +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.io.util.FileUtils; + +import static org.apache.cassandra.io.util.File.WriteMode.OVERWRITE; +import static org.apache.cassandra.stress.settings.SettingsCredentials.CQL_PASSWORD_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.CQL_USERNAME_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.JMX_PASSWORD_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.JMX_USERNAME_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY; +import static org.apache.cassandra.stress.settings.SettingsCredentials.TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY; +import static org.junit.Assert.assertEquals; + +public class SettingsCredentialsTest +{ + @Test + public void testReadCredentialsFromFileMixed() throws Exception + { + Properties properties = new Properties(); + properties.setProperty(CQL_USERNAME_PROPERTY_KEY, "cqluserfromfile"); + properties.setProperty(CQL_PASSWORD_PROPERTY_KEY, "cqlpasswordfromfile"); + properties.setProperty(JMX_USERNAME_PROPERTY_KEY, "jmxuserfromfile"); + properties.setProperty(JMX_PASSWORD_PROPERTY_KEY, "jmxpasswordfromfile"); + properties.setProperty(TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY, "keystorestorepasswordfromfile"); + properties.setProperty(TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY, "truststorepasswordfromfile"); + + File tempFile = FileUtils.createTempFile("cassandra-stress-credentials-test", "properties"); + + try (Writer w = tempFile.newWriter(OVERWRITE)) + { + properties.store(w, null); + } + + Map args = new HashMap<>(); + args.put("write", new String[]{}); + args.put("-mode", new String[]{ "cql3", "native", "password=cqlpasswordoncommandline" }); + args.put("-transport", new String[]{ "truststore=sometruststore", "keystore=somekeystore" }); + args.put("-jmx", new String[]{ "password=jmxpasswordoncommandline" }); + args.put("-credentials-file", new String[]{ tempFile.absolutePath() }); + StressSettings settings = StressSettings.get(args); + + assertEquals("cqluserfromfile", settings.credentials.cqlUsername); + assertEquals("cqlpasswordfromfile", settings.credentials.cqlPassword); + assertEquals("jmxuserfromfile", settings.credentials.jmxUsername); + assertEquals("jmxpasswordfromfile", settings.credentials.jmxPassword); + assertEquals("keystorestorepasswordfromfile", settings.credentials.transportKeystorePassword); + assertEquals("truststorepasswordfromfile", settings.credentials.transportTruststorePassword); + + assertEquals("cqluserfromfile", settings.mode.username); + assertEquals("cqlpasswordoncommandline", settings.mode.password); + assertEquals("jmxuserfromfile", settings.jmx.user); + assertEquals("jmxpasswordoncommandline", settings.jmx.password); + assertEquals("keystorestorepasswordfromfile", settings.transport.getEncryptionOptions().keystore_password); + assertEquals("truststorepasswordfromfile", settings.transport.getEncryptionOptions().truststore_password); + } + + @Test + public void testReadCredentialsFromFileOverridenByCommandLine() throws Exception + { + Properties properties = new Properties(); + properties.setProperty(CQL_USERNAME_PROPERTY_KEY, "cqluserfromfile"); + properties.setProperty(CQL_PASSWORD_PROPERTY_KEY, "cqlpasswordfromfile"); + properties.setProperty(JMX_USERNAME_PROPERTY_KEY, "jmxuserfromfile"); + properties.setProperty(JMX_PASSWORD_PROPERTY_KEY, "jmxpasswordfromfile"); + properties.setProperty(TRANSPORT_KEYSTORE_PASSWORD_PROPERTY_KEY, "keystorestorepasswordfromfile"); + properties.setProperty(TRANSPORT_TRUSTSTORE_PASSWORD_PROPERTY_KEY, "truststorepasswordfromfile"); + + File tempFile = FileUtils.createTempFile("cassandra-stress-credentials-test", "properties"); + + try (Writer w = tempFile.newWriter(OVERWRITE)) + { + properties.store(w, null); + } + + Map args = new HashMap<>(); + args.put("write", new String[]{}); + args.put("-mode", new String[]{ "cql3", "native", "password=cqlpasswordoncommandline", "user=cqluseroncommandline" }); + args.put("-jmx", new String[]{ "password=jmxpasswordoncommandline", "user=jmxuseroncommandline" }); + args.put("-transport", new String[]{ "truststore=sometruststore", + "keystore=somekeystore", + "truststore-password=truststorepasswordfromcommandline", + "keystore-password=keystorepasswordfromcommandline" }); + args.put("-credentials-file", new String[]{ tempFile.absolutePath() }); + StressSettings settings = StressSettings.get(args); + + assertEquals("cqluserfromfile", settings.credentials.cqlUsername); + assertEquals("cqlpasswordfromfile", settings.credentials.cqlPassword); + assertEquals("jmxuserfromfile", settings.credentials.jmxUsername); + assertEquals("jmxpasswordfromfile", settings.credentials.jmxPassword); + assertEquals("keystorestorepasswordfromfile", settings.credentials.transportKeystorePassword); + assertEquals("truststorepasswordfromfile", settings.credentials.transportTruststorePassword); + + assertEquals("cqluseroncommandline", settings.mode.username); + assertEquals("cqlpasswordoncommandline", settings.mode.password); + assertEquals("jmxuseroncommandline", settings.jmx.user); + assertEquals("jmxpasswordoncommandline", settings.jmx.password); + assertEquals("truststorepasswordfromcommandline", settings.transport.getEncryptionOptions().truststore_password); + assertEquals("keystorepasswordfromcommandline", settings.transport.getEncryptionOptions().keystore_password); + } +}