diff --git a/LICENSE-commons-codec.txt b/3rd-party-licenses/LICENSE-commons-codec.txt
similarity index 100%
rename from LICENSE-commons-codec.txt
rename to 3rd-party-licenses/LICENSE-commons-codec.txt
diff --git a/LICENSE-httpclient.txt b/3rd-party-licenses/LICENSE-httpclient.txt
similarity index 100%
rename from LICENSE-httpclient.txt
rename to 3rd-party-licenses/LICENSE-httpclient.txt
diff --git a/LICENSE-httpcore.txt b/3rd-party-licenses/LICENSE-httpcore.txt
similarity index 100%
rename from LICENSE-httpcore.txt
rename to 3rd-party-licenses/LICENSE-httpcore.txt
diff --git a/LICENSE-jersey-apache-client4.txt b/3rd-party-licenses/LICENSE-jersey-apache-client4.txt
similarity index 100%
rename from LICENSE-jersey-apache-client4.txt
rename to 3rd-party-licenses/LICENSE-jersey-apache-client4.txt
diff --git a/LICENSE-jersey-client.txt b/3rd-party-licenses/LICENSE-jersey-client.txt
similarity index 100%
rename from LICENSE-jersey-client.txt
rename to 3rd-party-licenses/LICENSE-jersey-client.txt
diff --git a/LICENSE-jersey-core.txt b/3rd-party-licenses/LICENSE-jersey-core.txt
similarity index 100%
rename from LICENSE-jersey-core.txt
rename to 3rd-party-licenses/LICENSE-jersey-core.txt
diff --git a/LICENSE-log4j.txt b/3rd-party-licenses/LICENSE-log4j.txt
similarity index 100%
rename from LICENSE-log4j.txt
rename to 3rd-party-licenses/LICENSE-log4j.txt
diff --git a/build.gradle b/build.gradle
index 0cab1a5..243f599 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,7 +29,7 @@ description = 'Smart REST Client - JAX-RS (Jersey) REST client that provides cli
ext.githubProjectName = 'smart-client-java'
buildscript {
- ext.commonBuildVersion = '1.3.2'
+ ext.commonBuildVersion = '1.3.3'
ext.commonBuildDir = "https://raw.githubusercontent.com/emcvipr/ecs-common-build/v$commonBuildVersion"
apply from: "$commonBuildDir/ecs-publish.buildscript.gradle", to: buildscript
}
@@ -39,7 +39,7 @@ apply from: "$commonBuildDir/ecs-publish.gradle"
dependencies {
compile 'com.sun.jersey:jersey-client:1.19',
'com.sun.jersey.contribs:jersey-apache-client4:1.19',
- 'org.apache.httpcomponents:httpclient:4.5',
+ 'org.apache.httpcomponents:httpclient:4.2.6',
'log4j:log4j:1.2.17'
testCompile 'junit:junit:4.12'
}
diff --git a/src/main/java/com/emc/rest/smart/HostListProvider.java b/src/main/java/com/emc/rest/smart/HostListProvider.java
index 2f34080..eb1a10c 100644
--- a/src/main/java/com/emc/rest/smart/HostListProvider.java
+++ b/src/main/java/com/emc/rest/smart/HostListProvider.java
@@ -37,4 +37,13 @@ public interface HostListProvider {
* (host.setHealthy(false)
is called).
*/
void runHealthCheck(Host host);
+
+ /**
+ * Destroy this provider. Any system resources associated with the provider
+ * will be cleaned up.
+ *
+ * The provider must not be reused after this method is called otherwise
+ * undefined behavior will occur.
+ */
+ void destroy();
}
diff --git a/src/main/java/com/emc/rest/smart/LoadBalancer.java b/src/main/java/com/emc/rest/smart/LoadBalancer.java
index 1df21e3..97e4d34 100644
--- a/src/main/java/com/emc/rest/smart/LoadBalancer.java
+++ b/src/main/java/com/emc/rest/smart/LoadBalancer.java
@@ -42,9 +42,9 @@ public LoadBalancer(List initialHosts) {
* Returns the host with the lowest response index.
*/
public Host getTopHost(Map requestProperties) {
- Host topHost = null;
+ Host topHost = null, topHealthyHost = null;
- long lowestIndex = Long.MAX_VALUE;
+ long lowestIndex = Long.MAX_VALUE, lowestHealthyIndex = Long.MAX_VALUE;
synchronized (hosts) {
for (Host host : hosts) {
@@ -52,9 +52,6 @@ public Host getTopHost(Map requestProperties) {
// apply any veto rules
if (shouldVeto(host, requestProperties)) continue;
- // if the host is unhealthy/down, ignore it
- if (!host.isHealthy()) continue;
-
// get response index for a host
long hostIndex = host.getResponseIndex();
@@ -63,8 +60,17 @@ public Host getTopHost(Map requestProperties) {
topHost = host;
lowestIndex = hostIndex;
}
+
+ // also keep track of the top *healthy* host
+ if (host.isHealthy() && hostIndex < lowestHealthyIndex) {
+ topHealthyHost = host;
+ lowestHealthyIndex = hostIndex;
+ }
}
+ // if there are no healthy hosts, we still need a host to contact
+ if (topHealthyHost != null) topHost = topHealthyHost;
+
// move the top host to the end of the host list as an extra tie-breaker
hosts.remove(topHost);
hosts.add(topHost);
diff --git a/src/main/java/com/emc/rest/smart/PollingDaemon.java b/src/main/java/com/emc/rest/smart/PollingDaemon.java
index a37944e..3bcf881 100644
--- a/src/main/java/com/emc/rest/smart/PollingDaemon.java
+++ b/src/main/java/com/emc/rest/smart/PollingDaemon.java
@@ -99,4 +99,12 @@ public void run() {
public void terminate() {
running = false;
}
+
+ public SmartConfig getSmartConfig() {
+ return smartConfig;
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
}
diff --git a/src/main/java/com/emc/rest/smart/SmartClientFactory.java b/src/main/java/com/emc/rest/smart/SmartClientFactory.java
index 4d9432b..951c3da 100644
--- a/src/main/java/com/emc/rest/smart/SmartClientFactory.java
+++ b/src/main/java/com/emc/rest/smart/SmartClientFactory.java
@@ -36,9 +36,16 @@
import com.sun.jersey.core.impl.provider.entity.ByteArrayProvider;
import com.sun.jersey.core.impl.provider.entity.FileProvider;
import com.sun.jersey.core.impl.provider.entity.InputStreamProvider;
+import org.apache.http.impl.client.AbstractHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.log4j.Logger;
public final class SmartClientFactory {
+ private static final Logger l4j = Logger.getLogger(SmartClientFactory.class);
+
+ public static final String DISABLE_APACHE_RETRY = "com.emc.rest.smart.disableApacheRetry";
+
public static Client createSmartClient(SmartConfig smartConfig) {
return createSmartClient(smartConfig, createApacheClientHandler(smartConfig));
}
@@ -96,9 +103,31 @@ public static Client createStandardClient(SmartConfig smartConfig,
clientConfig.getClasses().add(InputStreamProvider.class);
// build Jersey client
- Client client = new Client(clientHandler, clientConfig);
+ return new Client(clientHandler, clientConfig);
+ }
- return client;
+ /**
+ * Destroy this client. Any system resources associated with the client
+ * will be cleaned up.
+ *
+ * This method must be called when there are not responses pending otherwise
+ * undefined behavior will occur.
+ *
+ * The client must not be reused after this method is called otherwise
+ * undefined behavior will occur.
+ */
+ public static void destroy(Client client) {
+ PollingDaemon pollingDaemon = (PollingDaemon) client.getProperties().get(PollingDaemon.PROPERTY_KEY);
+ if (pollingDaemon != null) {
+ l4j.debug("terminating polling daemon");
+ pollingDaemon.terminate();
+ if (pollingDaemon.getSmartConfig().getHostListProvider() != null) {
+ l4j.debug("destroying host list provider");
+ pollingDaemon.getSmartConfig().getHostListProvider().destroy();
+ }
+ }
+ l4j.debug("destroying Jersey client");
+ client.destroy();
}
static ApacheHttpClient4Handler createApacheClientHandler(SmartConfig smartConfig) {
@@ -119,7 +148,13 @@ static ApacheHttpClient4Handler createApacheClientHandler(SmartConfig smartConfi
if (smartConfig.getProxyPass() != null)
clientConfig.getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_PASSWORD, smartConfig.getProxyPass());
- return ApacheHttpClient4.create(clientConfig).getClientHandler();
+ ApacheHttpClient4Handler handler = ApacheHttpClient4.create(clientConfig).getClientHandler();
+
+ // disable the retry handler if necessary
+ if (smartConfig.getProperty(DISABLE_APACHE_RETRY) != null)
+ ((AbstractHttpClient) handler.getHttpClient()).setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
+
+ return handler;
}
private SmartClientFactory() {
diff --git a/src/main/java/com/emc/rest/smart/SmartConfig.java b/src/main/java/com/emc/rest/smart/SmartConfig.java
index f234577..306c7db 100644
--- a/src/main/java/com/emc/rest/smart/SmartConfig.java
+++ b/src/main/java/com/emc/rest/smart/SmartConfig.java
@@ -146,7 +146,7 @@ public Object getProperty(String propName) {
/**
* Allows custom Jersey client properties to be set. These will be passed on in the Jersey ClientConfig
*/
- public void withProperty(String propName, Object value) {
+ public void setProperty(String propName, Object value) {
properties.put(propName, value);
}
@@ -184,4 +184,9 @@ public SmartConfig withHealthCheckEnabled(boolean healthCheckEnabled) {
setHealthCheckEnabled(healthCheckEnabled);
return this;
}
+
+ public SmartConfig withProperty(String propName, Object value) {
+ setProperty(propName, value);
+ return this;
+ }
}
diff --git a/src/main/java/com/emc/rest/smart/ecs/EcsHostListProvider.java b/src/main/java/com/emc/rest/smart/ecs/EcsHostListProvider.java
index 820bf82..8ac229d 100644
--- a/src/main/java/com/emc/rest/smart/ecs/EcsHostListProvider.java
+++ b/src/main/java/com/emc/rest/smart/ecs/EcsHostListProvider.java
@@ -102,6 +102,11 @@ public void runHealthCheck(Host host) {
client.resource(getRequestUri(host, "/?ping")).header("x-emc-namespace", "x").get(String.class);
}
+ @Override
+ public void destroy() {
+ client.destroy();
+ }
+
protected List getDataNodes(Host host) {
String path = "/?endpoint";
URI uri = getRequestUri(host, path);
diff --git a/src/test/java/com/emc/rest/smart/TestHealthCheck.java b/src/test/java/com/emc/rest/smart/TestHealthCheck.java
index 80a626d..aee4c11 100644
--- a/src/test/java/com/emc/rest/smart/TestHealthCheck.java
+++ b/src/test/java/com/emc/rest/smart/TestHealthCheck.java
@@ -140,6 +140,10 @@ public TestHostListProvider(Host host, boolean healthy) {
this.healthy = healthy;
}
+ @Override
+ public void destroy() {
+ }
+
@Override
public List getHostList() {
throw new RuntimeException("no host update");