org.voltdb.client.ClientConfig Maven / Gradle / Ivy
/* This file is part of VoltDB.
* Copyright (C) 2008-2020 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see .
*/
package org.voltdb.client;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.math.RoundingMode;
import java.security.Principal;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.voltcore.utils.ssl.SSLConfiguration;
import org.voltcore.utils.ssl.SSLConfiguration.SslConfig;
import org.voltdb.common.Constants;
import org.voltdb.types.VoltDecimalHelper;
/**
* Container for configuration settings for a Client
*/
public class ClientConfig {
private static final String DEFAULT_SSL_PROPS_FILE = "ssl-config";
static final long DEFAULT_PROCEDURE_TIMOUT_NANOS = TimeUnit.MINUTES.toNanos(2);// default timeout is 2 minutes;
static final long DEFAULT_CONNECTION_TIMOUT_MS = 2 * 60 * 1000; // default timeout is 2 minutes;
static final long DEFAULT_INITIAL_CONNECTION_RETRY_INTERVAL_MS = 1000; // default initial connection retry interval is 1 second
static final long DEFAULT_MAX_CONNECTION_RETRY_INTERVAL_MS = 8000; // default max connection retry interval is 8 seconds
final ClientAuthScheme m_hashScheme;
final String m_username;
final String m_password;
final boolean m_cleartext;
final ClientStatusListenerExt m_listener;
boolean m_heavyweight = false;
int m_maxOutstandingTxns = 3000;
int m_maxTransactionsPerSecond = Integer.MAX_VALUE;
boolean m_autoTune = false;
int m_autoTuneTargetInternalLatency = 5;
long m_procedureCallTimeoutNanos = DEFAULT_PROCEDURE_TIMOUT_NANOS;
long m_connectionResponseTimeoutMS = DEFAULT_CONNECTION_TIMOUT_MS;
boolean m_useClientAffinity = true;
Subject m_subject = null;
boolean m_reconnectOnConnectionLoss;
long m_initialConnectionRetryIntervalMS = DEFAULT_INITIAL_CONNECTION_RETRY_INTERVAL_MS;
long m_maxConnectionRetryIntervalMS = DEFAULT_MAX_CONNECTION_RETRY_INTERVAL_MS;
boolean m_sendReadsToReplicasBytDefaultIfCAEnabled = false;
SslConfig m_sslConfig;
boolean m_topologyChangeAware = false;
boolean m_enableSSL = false;
String m_sslPropsFile = null;
//For unit testing. This should really be in Environment class we should assemble all such there.
public static final boolean ENABLE_SSL_FOR_TEST = Boolean.valueOf(
System.getenv("ENABLE_SSL") == null ?
Boolean.toString(Boolean.getBoolean("ENABLE_SSL"))
: System.getenv("ENABLE_SSL"));
final static String getUserNameFromSubject(Subject subject) {
if (subject == null || subject.getPrincipals() == null || subject.getPrincipals().isEmpty()) {
throw new IllegalArgumentException("Subject is null or does not contain principals");
}
Iterator piter = subject.getPrincipals().iterator();
Principal principal = piter.next();
String username = principal.getName();
while (piter.hasNext()) {
principal = piter.next();
if (principal instanceof DelegatePrincipal) {
username = principal.getName();
break;
}
}
return username;
}
/**
* Configuration for a client with no authentication credentials that will
* work with a server with security disabled. Also specifies no status listener.
*/
public ClientConfig() {
this("", "", true, (ClientStatusListenerExt) null, ClientAuthScheme.HASH_SHA256);
}
/**
* Configuration for a client that specifies authentication credentials. The username and
* password can be null or the empty string.
*
* @param username Cleartext username.
* @param password Cleartext password.
*/
public ClientConfig(String username, String password) {
this(username, password, true, (ClientStatusListenerExt) null, ClientAuthScheme.HASH_SHA256);
}
/**
* Configuration for a client that specifies authentication credentials. The username and
* password can be null or the empty string. Also specifies a status listener.
*
* @deprecated {@link ClientStatusListener} deprecated in favor of {@link ClientStatusListenerExt}
* in
* @param username Cleartext username.
* @param password Cleartext password.
* @param scheme Client password hash scheme
* @param listener {@link ClientStatusListener} implementation to receive callbacks.
*/
@Deprecated
public ClientConfig(String username, String password, ClientStatusListener listener, ClientAuthScheme scheme) {
this(username, password, true, new ClientStatusListenerWrapper(listener), scheme);
}
/**
* Configuration for a client that specifies authentication credentials. The username and
* password can be null or the empty string. Also specifies a status listener.
*
* @param username Cleartext username.
* @param password Cleartext password.
* @param listener {@link ClientStatusListenerExt} implementation to receive callbacks.
*/
public ClientConfig(String username, String password, ClientStatusListenerExt listener) {
this(username,password,true,listener, ClientAuthScheme.HASH_SHA256);
}
/**
* Configuration for a client that specifies authentication credentials. The username and
* password can be null or the empty string. Also specifies a status listener.
*
* @param username Cleartext username.
* @param password Cleartext password.
* @param listener {@link ClientStatusListenerExt} implementation to receive callbacks.
* @param scheme Client password hash scheme
*/
public ClientConfig(String username, String password, ClientStatusListenerExt listener, ClientAuthScheme scheme) {
this(username,password,true,listener, scheme);
}
/**
* Configuration for a client that specifies authentication credentials. The username and
* password can be null or the empty string. Also specifies a status listener.
*
* @param username Cleartext username.
* @param password A cleartext or hashed passowrd.
* @param listener {@link ClientStatusListenerExt} implementation to receive callbacks.
* @param cleartext Whether the password is hashed.
*/
public ClientConfig(String username, String password, boolean cleartext, ClientStatusListenerExt listener) {
this(username, password, cleartext, listener, ClientAuthScheme.HASH_SHA256);
}
/**
* Configuration for a client that specifies an already authenticated {@link Subject}.
* Also specifies a status listener.
*
* @param subject an authenticated {@link Subject}
* @param listener {@link ClientStatusListenerExt} implementation to receive callbacks.
*/
public ClientConfig(Subject subject, ClientStatusListenerExt listener) {
this(getUserNameFromSubject(subject), "", true, listener, ClientAuthScheme.HASH_SHA256);
m_subject = subject;
}
/**
* Configuration for a client that specifies authentication credentials. The username and
* password can be null or the empty string. Also specifies a status listener.
*
* @param username Cleartext username.
* @param password A cleartext or hashed passowrd.
* @param listener {@link ClientStatusListenerExt} implementation to receive callbacks.
* @param cleartext Whether the password is hashed.
* @param scheme Client password hash scheme
*/
public ClientConfig(String username, String password, boolean cleartext, ClientStatusListenerExt listener, ClientAuthScheme scheme) {
if (ClientConfig.ENABLE_SSL_FOR_TEST) {
try (InputStream is = ClientConfig.class.getResourceAsStream(DEFAULT_SSL_PROPS_FILE)) {
Properties sslProperties = new Properties();
sslProperties.load(is);
String trustStorePath = sslProperties.getProperty(SSLConfiguration.TRUSTSTORE_CONFIG_PROP);
String trustStorePassword = sslProperties.getProperty(SSLConfiguration.TRUSTSTORE_PASSWORD_CONFIG_PROP);
setTrustStore(trustStorePath, trustStorePassword);
enableSSL();
} catch (IOException e) {
throw new IllegalArgumentException("Unable to access SSL configuration.", e);
}
}
if (username == null) {
m_username = "";
} else {
m_username = username;
}
if (password == null) {
m_password = "";
} else {
m_password = password;
}
m_listener = listener;
m_cleartext = cleartext;
m_hashScheme = scheme;
}
/**
* Set the timeout for procedure call. If the timeout expires before the call returns,
* the procedure callback will be called with status {@link ClientResponse#CONNECTION_TIMEOUT}.
* Synchronous procedures will throw an exception. If a response comes back after the
* expiration has triggered, then a callback method
* {@link ClientStatusListenerExt#lateProcedureResponse(ClientResponse, String, int)}
* will be called.
*
* Default value is 2 minutes if not set. Value of 0 means forever.
*
* Note that while specified in MS, this timeout is only accurate to within a second or so.
*
* @param ms Timeout value in milliseconds.
*/
public void setProcedureCallTimeout(long ms) {
assert(ms >= 0);
if (ms < 0) ms = 0;
// 0 implies infinite, but use LONG_MAX to reduce branches to test
if (ms == 0) ms = Long.MAX_VALUE;
m_procedureCallTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(ms);
}
/**
* Set the timeout for reading from a connection. If a connection receives no responses,
* either from procedure calls or &Pings, for the timeout time in milliseconds,
* then the connection will be assumed dead and the closed connection callback will
* be called.
*
* Default value is 2 minutes if not set. Value of 0 means forever.
*
* Note that while specified in MS, this timeout is only accurate to within a second or so.
*
* @param ms Timeout value in milliseconds.
*/
public void setConnectionResponseTimeout(long ms) {
assert(ms >= 0);
if (ms < 0) ms = 0;
// 0 implies infinite, but use LONG_MAX to reduce branches to test
if (ms == 0) ms = Long.MAX_VALUE;
m_connectionResponseTimeoutMS = ms;
}
/**
* Set the maximum size of memory pool arenas before falling back to using heap byte buffers.
*
* @deprecated Deprecated because memory pooling no longer uses arenas. Has no effect.
* @param maxArenaSizes Maximum size of each arena.
*/
@Deprecated
public void setMaxArenaSizes(int maxArenaSizes[]) {
}
/**
* By default a single network thread is created and used to do IO and invoke callbacks.
* When set to true, Runtime.getRuntime().availableProcessors() / 2 threads are created.
* Multiple server connections are required for more threads to be involved, a connection
* is assigned exclusively to a connection.
*
* @param heavyweight Whether to create additional threads for high IO or
* high processing workloads.
*/
public void setHeavyweight(boolean heavyweight) {
m_heavyweight = heavyweight;
}
/**
* Provide a hint indicating how large messages will be once serialized. Ensures
* efficient message buffer allocation.
*
* @deprecated Has no effect.
* @param size The expected size of the outgoing message.
*/
@Deprecated
public void setExpectedOutgoingMessageSize(int size) {
}
/**
* Set the maximum number of outstanding requests that will be submitted before
* blocking. Similar to the number of concurrent connections in a traditional synchronous API.
* Defaults to 2k.
*
* @param maxOutstanding The maximum outstanding transactions before calls to
* {@link Client#callProcedure(ProcedureCallback, String, Object...)} will block
* or return false (depending on settings).
*/
public void setMaxOutstandingTxns(int maxOutstanding) {
if (maxOutstanding < 1) {
throw new IllegalArgumentException(
"Max outstanding must be greater than 0, " + maxOutstanding + " was specified");
}
m_maxOutstandingTxns = maxOutstanding;
}
/**
* Set the maximum number of transactions that can be run in 1 second. Note this
* specifies a rate, not a ceiling. If the limit is set to 10, you can't send 10 in
* the first half of the second and 5 in the later half; the client will let you send
* about 1 transaction every 100ms. Default is {link Integer#MAX_VALUE}.
*
* @param maxTxnsPerSecond Requested ceiling on rate of call in transaction per second.
*/
public void setMaxTransactionsPerSecond(int maxTxnsPerSecond) {
if (maxTxnsPerSecond < 1) {
throw new IllegalArgumentException(
"Max TPS must be greater than 0, " + maxTxnsPerSecond + " was specified");
}
m_maxTransactionsPerSecond = maxTxnsPerSecond;
}
/**
* Enable the Auto Tuning feature, which dynamically adjusts the maximum
* allowable transaction number with the goal of maintaining a target latency.
* The latency value used is the internal latency as reported by the servers.
* The internal latency is a good measure of system saturation.
*
* See {@link #setAutoTuneTargetInternalLatency(int)}.
*/
public void enableAutoTune() {
m_autoTune = true;
}
/**
* Attempts to route transactions to the correct master partition improving latency
* and throughput
*
* If you are using persistent connections you definitely want this.
*
* Defaults to TRUE.
*
* @param on Enable or disable the affinity feature.
*/
public void setClientAffinity(boolean on) {
m_useClientAffinity = on;
}
/**
* Attempts to connect to all nodes in the cluster
* Defaults to false.
* @param enabled Enable or disable the topology awareness feature.
*/
public void setTopologyChangeAware(boolean enabled) {
m_topologyChangeAware = enabled;
}
/**
* By default, reads are sent to the leader replica for each partition. This
* is usually optimal for the default read consistency value, SAFE. If you are
* using FAST reads, enabling this setting will load balance reads amongst
* partition replicas, often increasing throughput and decreasing latency.
*
* Note: consistency modality SAFE/FAST is obsolete and no longer supported.
*
*
* Defaults to FALSE. Has no effect if Client Affinity is disabled.
*
* @param on Enable or disable sending reads to replicas.
*/
public void setSendReadsToReplicasByDefault(boolean on) {
m_sendReadsToReplicasBytDefaultIfCAEnabled = on;
}
/**
* Attempts to reconnect to a node with retry after connection loss. See the {@link ReconnectStatusListener}.
*
* @param on Enable or disable the reconnection feature. Default is off.
*/
public void setReconnectOnConnectionLoss(boolean on) {
this.m_reconnectOnConnectionLoss = on;
}
/**
* Set the initial connection retry interval. Only takes effect if {@link #m_reconnectOnConnectionLoss} is turned on.
*
* @param ms initial connection retry interval in milliseconds.
*/
public void setInitialConnectionRetryInterval(long ms) {
this.m_initialConnectionRetryIntervalMS = ms;
}
/**
* Set the max connection retry interval. Only takes effect if {@link #m_reconnectOnConnectionLoss} is turned on.
*
* @param ms max connection retry interval in milliseconds.
*/
public void setMaxConnectionRetryInterval(long ms) {
this.m_maxConnectionRetryIntervalMS = ms;
}
/**
* Set the target latency for the Auto Tune feature. Note this represents internal
* latency as reported by the server(s), not round-trip latency measured by the
* client. Default value is 5 if this is not called.
*
* @param targetLatency New target latency in milliseconds.
*/
public void setAutoTuneTargetInternalLatency(int targetLatency) {
if (targetLatency < 1) {
throw new IllegalArgumentException(
"Max auto tune latency must be greater than 0, " + targetLatency + " was specified");
}
m_autoTuneTargetInternalLatency = targetLatency;
}
/**
* Enable Kerberos authentication with the provided subject credentials
* @param subject Identity of the authenticated user.
*/
public void enableKerberosAuthentication(final Subject subject) {
m_subject = subject;
}
/**
* Use the provided JAAS login context entry key to get the authentication
* credentials held by the caller
*
* @param loginContextEntryKey JAAS login context config entry designation
*/
public void enableKerberosAuthentication(final String loginContextEntryKey) {
try {
LoginContext lc = new LoginContext(loginContextEntryKey);
lc.login();
m_subject = lc.getSubject();
} catch (SecurityException | LoginException ex) {
throw new IllegalArgumentException("Cannot determine client consumer's credentials", ex);
}
}
/**
* Enable or disable the rounding mode in the client. This must match the
* rounding mode set in the server, which is set using system properties.
*
* @param isEnabled True iff rounding is enabled.
* @param mode The rounding mode, with values taken from java.math.RoundingMode.
*/
public static void setRoundingConfig(boolean isEnabled, RoundingMode mode) {
VoltDecimalHelper.setRoundingConfig(isEnabled, mode);
}
/**
* Configure trust store
*
* @param pathToTrustStore file specification for the trust store
* @param trustStorePassword trust store key file password
*/
public void setTrustStore(String pathToTrustStore, String trustStorePassword) {
File tsFD = new File(pathToTrustStore != null && !pathToTrustStore.trim().isEmpty() ? pathToTrustStore : "");
if (!tsFD.exists() || !tsFD.isFile() || !tsFD.canRead()) {
throw new IllegalArgumentException("Trust store " + pathToTrustStore + " is not a read accessible file");
}
m_sslConfig = new SSLConfiguration.SslConfig(null, null, pathToTrustStore, trustStorePassword);
}
/**
* Configure trust store
*
* @param propFN property file name containing trust store properties:
*
* - {@code trustStore} trust store file specification
*
- {@code trustStorePassword} trust store password
*
*/
public void setTrustStoreConfigFromPropertyFile(String propFN) {
File propFD = new File(propFN != null && !propFN.trim().isEmpty() ? propFN : "");
if (!propFD.exists() || !propFD.isFile() || !propFD.canRead()) {
throw new IllegalArgumentException("Properties file " + propFN + " is not a read accessible file");
}
Properties props = new Properties();
try (FileReader fr = new FileReader(propFD)) {
props.load(fr);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to read properties file " + propFN, e);
}
String trustStore = props.getProperty(SSLConfiguration.TRUSTSTORE_CONFIG_PROP);
String trustStorePassword = props.getProperty(SSLConfiguration.TRUSTSTORE_PASSWORD_CONFIG_PROP);
m_sslConfig = new SSLConfiguration.SslConfig(null, null, trustStore, trustStorePassword);
}
/**
* Configure ssl from the provided properties file. if file is not provided we configure without keystore and truststore manager.
*/
public void enableSSL() {
m_enableSSL = true;
if (m_sslConfig == null) {
m_sslConfig = new SSLConfiguration.SslConfig();
}
}
public void setTrustStoreConfigFromDefault() {
String trustStore = Constants.DEFAULT_TRUSTSTORE_RESOURCE;
String trustStorePassword = Constants.DEFAULT_TRUSTSTORE_PASSWD;
m_sslConfig = new SSLConfiguration.SslConfig(null, null, trustStore, trustStorePassword);
}
}