diff --git a/exhibitor-core/pom.xml b/exhibitor-core/pom.xml
index 1e9811f7..9c0f155b 100644
--- a/exhibitor-core/pom.xml
+++ b/exhibitor-core/pom.xml
@@ -94,6 +94,11 @@
jersey-json
+
+ com.orbitz.consul
+ consul-client
+
+
org.apache.curator
curator-test
@@ -111,5 +116,11 @@
testng
test
+
+
+ com.pszymczyk.consul
+ embedded-consul
+ test
+
diff --git a/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulConfigProvider.java b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulConfigProvider.java
new file mode 100644
index 00000000..b7e79801
--- /dev/null
+++ b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulConfigProvider.java
@@ -0,0 +1,128 @@
+package com.netflix.exhibitor.core.config.consul;
+
+import com.netflix.exhibitor.core.config.*;
+import com.orbitz.consul.Consul;
+import com.orbitz.consul.KeyValueClient;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+public class ConsulConfigProvider implements ConfigProvider {
+ private static final Long DEFAULT_LOCK_TIMEOUT_MS = 5L * 60L * 1000L; // 5 minutes;
+ private final Consul consul;
+ private final Properties defaults;
+ private final String basePath;
+ private final String versionPath;
+ private final String propertiesPath;
+ private final ConsulKvLock lock;
+ private final String pseudoLockPath;
+ private final Long lockTimeoutMs;
+
+ /**
+ * @param consul consul client instance for connecting to consul cluster
+ * @param prefix consul key-value path under which configs are stored
+ * @param defaults default properties
+ */
+ public ConsulConfigProvider(Consul consul, String prefix, Properties defaults) {
+ this(consul, prefix, defaults, DEFAULT_LOCK_TIMEOUT_MS);
+ }
+
+ /**
+ * @param consul consul client instance for connecting to consul cluster
+ * @param prefix consul key-value path under which configs are stored
+ * @param defaults default properties
+ * @param lockTimeoutMs timeout, in milliseconds, for lock acquisition
+ */
+ public ConsulConfigProvider(Consul consul, String prefix, Properties defaults, Long lockTimeoutMs) {
+ this.consul = consul;
+ this.defaults = defaults;
+ this.lockTimeoutMs = lockTimeoutMs;
+
+ this.basePath = prefix.endsWith("/") ? prefix : prefix + "/";
+ this.versionPath = basePath + "version";
+ this.propertiesPath = basePath + "properties";
+ this.pseudoLockPath = basePath + "pseudo-locks";
+
+ this.lock = new ConsulKvLock(consul, basePath + "lock", "exhibitor");
+ }
+
+ @Override
+ public void start() throws Exception {
+ // NOP
+ }
+
+ @Override
+ public void close() throws IOException {
+ // NOP
+ }
+
+ @Override
+ public LoadedInstanceConfig loadConfig() throws Exception {
+ ConsulVersionedProperties properties;
+
+ lock.acquireLock(lockTimeoutMs, TimeUnit.MILLISECONDS);
+ try {
+ properties = loadProperties();
+ }
+ finally {
+ lock.releaseLock();
+ }
+
+ PropertyBasedInstanceConfig config = new PropertyBasedInstanceConfig(
+ properties.getProperties(), defaults);
+ return new LoadedInstanceConfig(config, properties.getVersion());
+ }
+
+ @Override
+ public LoadedInstanceConfig storeConfig(ConfigCollection config, long compareVersion) throws Exception {
+ Long currentVersion = loadProperties().getVersion();
+ if (currentVersion != compareVersion) {
+ return null;
+ }
+
+ KeyValueClient kv = consul.keyValueClient();
+ PropertyBasedInstanceConfig instanceConfig = new PropertyBasedInstanceConfig(config);
+ StringWriter writer = new StringWriter();
+ instanceConfig.getProperties().store(writer, "Auto-generated by Exhibitor");
+
+ lock.acquireLock(lockTimeoutMs, TimeUnit.MILLISECONDS);
+ try {
+ kv.putValue(propertiesPath, writer.toString());
+ kv.putValue(versionPath, String.valueOf(currentVersion + 1));
+ }
+ finally {
+ lock.releaseLock();
+ }
+
+ return new LoadedInstanceConfig(instanceConfig, currentVersion + 1);
+ }
+
+ @Override
+ public PseudoLock newPseudoLock() throws Exception {
+ return new ConsulPseudoLock(consul, pseudoLockPath);
+ }
+
+ private String getString(String path) {
+ return consul.keyValueClient().getValueAsString(path).orNull();
+ }
+
+ private Long getLong(String path) {
+ return Long.valueOf(consul.keyValueClient().getValueAsString(path).or("0"));
+ }
+
+ private ConsulVersionedProperties loadProperties() throws Exception {
+ Long version = getLong(versionPath);
+
+ Properties properties = new Properties();
+ String rawProperties = getString(propertiesPath);
+ if (rawProperties != null) {
+ StringReader reader = new StringReader(getString(propertiesPath));
+ properties.load(reader);
+ }
+
+ return new ConsulVersionedProperties(properties, version);
+ }
+}
diff --git a/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulKvLock.java b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulKvLock.java
new file mode 100644
index 00000000..62548818
--- /dev/null
+++ b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulKvLock.java
@@ -0,0 +1,92 @@
+package com.netflix.exhibitor.core.config.consul;
+
+import com.google.common.base.Optional;
+import com.orbitz.consul.Consul;
+import com.orbitz.consul.KeyValueClient;
+import com.orbitz.consul.model.kv.Value;
+import com.orbitz.consul.model.session.ImmutableSession;
+import com.orbitz.consul.option.QueryOptions;
+
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+
+public class ConsulKvLock {
+ private final Consul consul;
+ private final String path;
+ private final String name;
+ private final String ttl;
+ private String sessionId;
+
+ /**
+ * @param consul consul client instance for connecting to consul cluster
+ * @param path consul key-value path to lock
+ * @param name a descriptive name for the lock
+ * @param ttl TTL, in seconds, for the consul session underpinning the lock
+ */
+ public ConsulKvLock(Consul consul, String path, String name, Integer ttl) {
+ this.consul = consul;
+ this.path = path;
+ this.name = name;
+ this.ttl = ttl != null ? String.format("%ds", ttl) : null;
+ this.sessionId = null;
+ }
+
+ /**
+ * @param consul consul client instance for connecting to consul cluster
+ * @param path consul key-value path to lock
+ * @param name a descriptive name for the lock
+ */
+ public ConsulKvLock(Consul consul, String path, String name) {
+ this(consul, path, name, 60);
+ }
+
+ private String createSession() {
+ final ImmutableSession session = ImmutableSession.builder()
+ .name(name)
+ .ttl(Optional.fromNullable(ttl))
+ .build();
+ return consul.sessionClient().createSession(session).getId();
+ }
+
+ private void destroySession() {
+ consul.sessionClient().destroySession(sessionId);
+ sessionId = null;
+ }
+
+ public boolean acquireLock(long maxWait, TimeUnit unit) {
+ KeyValueClient kv = consul.keyValueClient();
+ sessionId = createSession();
+
+ Optional value = kv.getValue(path);
+
+ if (kv.acquireLock(path, sessionId)) {
+ return true;
+ }
+
+ BigInteger index = BigInteger.valueOf(value.get().getModifyIndex());
+ kv.getValue(path, QueryOptions.blockMinutes((int) unit.toMinutes(maxWait), index).build());
+
+ if (!kv.acquireLock(path, sessionId)) {
+ destroySession();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void releaseLock() {
+ try {
+ KeyValueClient kv = consul.keyValueClient();
+ Optional value = kv.getValue(path);
+
+ if (value.isPresent()) {
+ Optional session = value.get().getSession();
+ if (session.isPresent()) {
+ kv.releaseLock(path, session.get());
+ }
+ }
+ } finally {
+ destroySession();
+ }
+ }
+}
diff --git a/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulPseudoLock.java b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulPseudoLock.java
new file mode 100644
index 00000000..cdeaf18c
--- /dev/null
+++ b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulPseudoLock.java
@@ -0,0 +1,33 @@
+package com.netflix.exhibitor.core.config.consul;
+
+import com.netflix.exhibitor.core.activity.ActivityLog;
+import com.netflix.exhibitor.core.config.PseudoLock;
+import com.orbitz.consul.Consul;
+
+import java.util.concurrent.TimeUnit;
+
+public class ConsulPseudoLock implements PseudoLock {
+
+ private final ConsulKvLock lock;
+
+ public ConsulPseudoLock(Consul consul, String prefix) {
+ String path = prefix.endsWith("/") ? prefix + "pseudo-lock" : prefix + "/pseudo-lock";
+ this.lock = new ConsulKvLock(consul, path, "pseudo-lock");
+ }
+
+ @Override
+ public boolean lock(ActivityLog log, long maxWait, TimeUnit unit) throws Exception {
+ if (!lock.acquireLock(maxWait, unit)) {
+ log.add(ActivityLog.Type.ERROR,
+ String.format("Could not acquire lock within %d ms", unit.toMillis(maxWait)));
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void unlock() throws Exception {
+ lock.releaseLock();
+ }
+}
diff --git a/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulVersionedProperties.java b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulVersionedProperties.java
new file mode 100644
index 00000000..da291bb0
--- /dev/null
+++ b/exhibitor-core/src/main/java/com/netflix/exhibitor/core/config/consul/ConsulVersionedProperties.java
@@ -0,0 +1,21 @@
+package com.netflix.exhibitor.core.config.consul;
+
+import java.util.Properties;
+
+public class ConsulVersionedProperties {
+ private final Properties properties;
+ private final Long version;
+
+ public ConsulVersionedProperties(Properties properties, Long version) {
+ this.properties = properties;
+ this.version = version;
+ }
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+}
diff --git a/exhibitor-core/src/test/java/com/netflix/exhibitor/core/config/consul/TestConsulConfigProvider.java b/exhibitor-core/src/test/java/com/netflix/exhibitor/core/config/consul/TestConsulConfigProvider.java
new file mode 100644
index 00000000..8bb8d8c3
--- /dev/null
+++ b/exhibitor-core/src/test/java/com/netflix/exhibitor/core/config/consul/TestConsulConfigProvider.java
@@ -0,0 +1,70 @@
+package com.netflix.exhibitor.core.config.consul;
+
+import com.google.common.net.HostAndPort;
+import com.netflix.exhibitor.core.config.LoadedInstanceConfig;
+import com.netflix.exhibitor.core.config.PropertyBasedInstanceConfig;
+import com.netflix.exhibitor.core.config.StringConfigs;
+import com.orbitz.consul.Consul;
+import com.orbitz.consul.model.session.SessionInfo;
+import com.pszymczyk.consul.ConsulProcess;
+import com.pszymczyk.consul.ConsulStarterBuilder;
+import org.apache.curator.test.Timing;
+import org.apache.curator.utils.CloseableUtils;
+import org.testng.Assert;
+import org.testng.annotations.*;
+
+import java.util.List;
+import java.util.Properties;
+
+
+public class TestConsulConfigProvider {
+ private Timing timing;
+ private ConsulProcess consul;
+ private Consul client;
+
+ @BeforeClass
+ public void setupClass() throws Exception {
+ consul = ConsulStarterBuilder.consulStarter().build().start();
+ }
+
+ @AfterClass
+ public void tearDownClass()
+ {
+ consul.close();
+ }
+
+ @BeforeMethod
+ public void setup() throws Exception {
+ timing = new Timing();
+
+ consul.reset();
+ client = Consul.builder()
+ .withHostAndPort(HostAndPort.fromParts("localhost", consul.getHttpPort()))
+ .build();
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ ConsulConfigProvider config = new ConsulConfigProvider(client, "prefix", new Properties());
+
+ try {
+ config.start();
+ config.loadConfig();
+
+ Properties properties = new Properties();
+ properties.setProperty(PropertyBasedInstanceConfig.toName(StringConfigs.ZOO_CFG_EXTRA, PropertyBasedInstanceConfig.ROOT_PROPERTY_PREFIX), "1,2,3");
+ config.storeConfig(new PropertyBasedInstanceConfig(properties, new Properties()), 0);
+
+ timing.sleepABit();
+
+ LoadedInstanceConfig instanceConfig = config.loadConfig();
+ Assert.assertEquals(instanceConfig.getConfig().getRootConfig().getString(StringConfigs.ZOO_CFG_EXTRA), "1,2,3");
+
+ List sessions = client.sessionClient().listSessions();
+ Assert.assertEquals(sessions.size(), 0, "Consul session still exists!");
+ }
+ finally {
+ CloseableUtils.closeQuietly(config);
+ }
+ }
+}
\ No newline at end of file
diff --git a/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCLI.java b/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCLI.java
index 3ee5febb..3e31a6f1 100644
--- a/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCLI.java
+++ b/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCLI.java
@@ -79,6 +79,14 @@ private OptionSection(String sectionName, Options options)
public static final String ZOOKEEPER_CONFIG_POLLING = "zkconfigpollms";
public static final String NONE_CONFIG_DIRECTORY = "noneconfigdir";
public static final String INITIAL_CONFIG_FILE = "defaultconfig";
+ public static final String CONSUL_CONFIG_HOST = "consulhost";
+ public static final String CONSUL_CONFIG_PORT = "consulport";
+ public static final String CONSUL_CONFIG_KEY_PREFIX = "consulprefix";
+ public static final String CONSUL_CONFIG_ACL_TOKEN = "consulacltoken";
+ public static final String CONSUL_CONFIG_SSL = "consulssl";
+ public static final String CONSUL_CONFIG_SSL_VERIFY_HOSTNAME = "consulsslverifyhostname";
+ public static final String CONSUL_CONFIG_SSL_PROTOCOL = "consulsslprotocol";
+ public static final String CONSUL_CONFIG_SSL_CA_CERT = "consulsslcacert";
public static final String FILESYSTEMBACKUP = "filesystembackup";
public static final String TIMEOUT = "timeout";
@@ -158,6 +166,16 @@ public ExhibitorCLI()
s3Options.addOption(null, S3_REGION, true, "Optional region for S3 calls (e.g. \"eu-west-1\"). Will be used to set the S3 client's endpoint.");
s3Options.addOption(null, S3_PROXY, true, "Optional configuration used when when connecting to S3 via a proxy. Argument is the path to an AWS credential properties file with four properties (only host, port and protocol are required if using a proxy): " + PropertyBasedS3ClientConfig.PROPERTY_S3_PROXY_HOST + ", " + PropertyBasedS3ClientConfig.PROPERTY_S3_PROXY_PORT + ", " + PropertyBasedS3ClientConfig.PROPERTY_S3_PROXY_USERNAME + ", " + PropertyBasedS3ClientConfig.PROPERTY_S3_PROXY_PASSWORD);
+ Options consulConfigOptions = new Options();
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_HOST, true, "Consul host; defaults to \"localhost\"");
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_PORT, true, "Consul HTTP(s) port; defaults to 8500");
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_ACL_TOKEN, true, "Optional Consul ACL token");
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_SSL, true, "If true, enables Consul communication over SSL");
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_SSL_VERIFY_HOSTNAME, true, "If true, verify SSL hostnames");
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_SSL_PROTOCOL, true, "Consul SSL/TLS protocol; defaults to \"TLSv1.2\"");
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_SSL_CA_CERT, true, "Path to the consul CA cert file");
+ consulConfigOptions.addOption(null, CONSUL_CONFIG_KEY_PREFIX, true, "Prefix in the key-value store under which to store Exhibitor data, e.g. \"exhibitor/\"");
+
generalOptions = new Options();
generalOptions.addOption(null, TIMEOUT, true, "Connection timeout (ms) for ZK connections. Default is 30000.");
generalOptions.addOption(null, LOGLINES, true, "Max lines of logging to keep in memory for display. Default is 1000.");
@@ -167,7 +185,7 @@ public ExhibitorCLI()
generalOptions.addOption(null, NODE_MUTATIONS, true, "If true, the Explorer UI will allow nodes to be modified (use with caution). Default is true.");
generalOptions.addOption(null, JQUERY_STYLE, true, "Styling used for the JQuery-based UI. Currently available options: " + getStyleOptions());
generalOptions.addOption(ALT_HELP, HELP, false, "Print this help");
- generalOptions.addOption(SHORT_CONFIG_TYPE, CONFIG_TYPE, true, "Defines which configuration type you want to use. Choices are: \"file\", \"s3\", \"zookeeper\" or \"none\". Additional config will be required depending on which type you are using.");
+ generalOptions.addOption(SHORT_CONFIG_TYPE, CONFIG_TYPE, true, "Defines which configuration type you want to use. Choices are: \"file\", \"s3\", \"zookeeper\", \"consul\" or \"none\". Additional config will be required depending on which type you are using.");
generalOptions.addOption(null, CONFIGCHECKMS, true, "Period (ms) to check for shared config updates. Default is: 30000");
generalOptions.addOption(null, SERVO_INTEGRATION, true, "true/false (default is false). If enabled, ZooKeeper will be queried once a minute for its state via the 'mntr' four letter word (this requires ZooKeeper 3.4.x+). Servo will be used to publish this data via JMX.");
generalOptions.addOption(null, INITIAL_CONFIG_FILE, true, "Full path to a file that contains initial/default values for Exhibitor/ZooKeeper config values. The file is a standard property file. The property names are listed below. The file can specify some or all of the properties.");
@@ -183,6 +201,7 @@ public ExhibitorCLI()
addAll("Configuration Options for Type \"s3\"", s3ConfigOptions);
addAll("Configuration Options for Type \"zookeeper\"", zookeeperConfigOptions);
addAll("Configuration Options for Type \"file\"", fileConfigOptions);
+ addAll("Configuration Options for Type \"consul\"", consulConfigOptions);
addAll("Configuration Options for Type \"none\"", noneConfigOptions);
addAll("Backup Options", backupOptions);
addAll("Authorization Options", authOptions);
diff --git a/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCreator.java b/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCreator.java
index da90e1da..5bbb6868 100644
--- a/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCreator.java
+++ b/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/ExhibitorCreator.java
@@ -19,6 +19,7 @@
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import com.google.common.net.HostAndPort;
import com.netflix.exhibitor.core.ExhibitorArguments;
import com.netflix.exhibitor.core.backup.BackupProvider;
import com.netflix.exhibitor.core.backup.filesystem.FileSystemBackupProvider;
@@ -30,6 +31,7 @@
import com.netflix.exhibitor.core.config.JQueryStyle;
import com.netflix.exhibitor.core.config.PropertyBasedInstanceConfig;
import com.netflix.exhibitor.core.config.StringConfigs;
+import com.netflix.exhibitor.core.config.consul.ConsulConfigProvider;
import com.netflix.exhibitor.core.config.filesystem.FileSystemConfigProvider;
import com.netflix.exhibitor.core.config.none.NoneConfigProvider;
import com.netflix.exhibitor.core.config.s3.S3ConfigArguments;
@@ -41,6 +43,7 @@
import com.netflix.exhibitor.core.s3.S3ClientFactoryImpl;
import com.netflix.exhibitor.core.servo.ServoRegistration;
import com.netflix.servo.jmx.JmxMonitorRegistry;
+import com.orbitz.consul.Consul;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.ParseException;
@@ -66,12 +69,20 @@
import org.mortbay.jetty.security.SecurityHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
@@ -295,6 +306,10 @@ else if ( configType.equals("zookeeper") )
{
configProvider = getZookeeperProvider(commandLine, useHostname, defaultProperties);
}
+ else if ( configType.equals("consul") )
+ {
+ configProvider = getConsulProvider(cli, commandLine, defaultProperties);
+ }
else if ( configType.equals("none") )
{
log.warn("Warning: you have intentionally turned off shared configuration. This mode is meant for special purposes only. Please verify that this is your intent.");
@@ -528,6 +543,54 @@ private ConfigProvider getS3Provider(ExhibitorCLI cli, CommandLine commandLine,
return new S3ConfigProvider(new S3ClientFactoryImpl(), awsCredentials, awsClientConfig, getS3Arguments(cli, commandLine.getOptionValue(S3_CONFIG), prefix), hostname, defaultProperties, s3Region);
}
+ private ConfigProvider getConsulProvider(ExhibitorCLI cli, CommandLine commandLine, Properties defaultProperties) throws Exception {
+ String host = commandLine.getOptionValue(CONSUL_CONFIG_HOST, "localhost");
+ Integer port = Integer.valueOf(commandLine.getOptionValue(CONSUL_CONFIG_PORT, "8500"));
+ String prefix = commandLine.getOptionValue(CONSUL_CONFIG_KEY_PREFIX, "exhibitor/");
+
+ Boolean sslEnabled = Boolean.valueOf(commandLine.getOptionValue(CONSUL_CONFIG_SSL, "false"));
+ String protocol = sslEnabled ? "https" : "http";
+ String url = String.format("%s://%s:%d", protocol, host, port);
+
+ Consul.Builder builder = Consul.builder().withUrl(url);
+ if (sslEnabled) {
+ if (!Boolean.valueOf(commandLine.getOptionValue(CONSUL_CONFIG_SSL_VERIFY_HOSTNAME, "true"))) {
+ builder.withHostnameVerifier(new NullHostnameVerifier());
+ }
+
+ String sslProtocol = commandLine.getOptionValue(CONSUL_CONFIG_SSL_PROTOCOL, "TLSv1.2");
+ String caCertPath = commandLine.getOptionValue(CONSUL_CONFIG_SSL_CA_CERT);
+ log.debug("SSL enabled for consul connections; protocol = %s, cacert = %s",
+ sslProtocol, caCertPath);
+
+ // Load cacert file
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cacert = (X509Certificate) cf.generateCertificate(new FileInputStream(caCertPath));
+
+ KeyStore trustStore = KeyStore.getInstance("JKS");
+ trustStore.load(null);
+ trustStore.setCertificateEntry("caCert", cacert);
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(trustStore);
+
+ SSLContext sslContext = SSLContext.getInstance(sslProtocol);
+ sslContext.init(null, tmf.getTrustManagers(), null);
+
+ builder.withSslContext(sslContext);
+ }
+ else {
+ log.debug("SSL is disabled for consul connections");
+ }
+
+ if (commandLine.hasOption(CONSUL_CONFIG_ACL_TOKEN)) {
+ builder.withAclToken(commandLine.getOptionValue(CONSUL_CONFIG_ACL_TOKEN));
+ }
+
+ Consul consul = builder.build();
+ return new ConsulConfigProvider(consul, prefix, defaultProperties);
+ }
+
private void checkMutuallyExclusive(ExhibitorCLI cli, CommandLine commandLine, String option1, String option2) throws ExhibitorCreatorExit
{
if ( commandLine.hasOption(option1) && commandLine.hasOption(option2) )
diff --git a/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/NullHostnameVerifier.java b/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/NullHostnameVerifier.java
new file mode 100644
index 00000000..6639fa31
--- /dev/null
+++ b/exhibitor-standalone/src/main/java/com/netflix/exhibitor/standalone/NullHostnameVerifier.java
@@ -0,0 +1,11 @@
+package com.netflix.exhibitor.standalone;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
+
+public class NullHostnameVerifier implements HostnameVerifier {
+ @Override
+ public boolean verify(String s, SSLSession sslSession) {
+ return true;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 9b25d0d1..d38425f1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,8 @@
1.8.5
3.4.5
1.7.0
+ 0.14.0
+ 0.3.1
@@ -200,6 +202,18 @@
mockito-core
${mockito-version}
+
+
+ com.orbitz.consul
+ consul-client
+ ${consul-version}
+
+
+
+ com.pszymczyk.consul
+ embedded-consul
+ ${embedded-consul-version}
+