Skip to content

Commit ab4e2cd

Browse files
author
arnett, stu
committed
v2.0.7
1 parent 5e0404b commit ab4e2cd

File tree

11 files changed

+295
-20
lines changed

11 files changed

+295
-20
lines changed

src/main/java/com/emc/rest/smart/Host.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@
3434
/**
3535
* Some basic statements about response index calculation:
3636
* <p>
37-
* - lower response index means the host is more likely to be used
38-
* - should be based primarily on number of open connections to the host
39-
* - an error will mark the host as unhealthy for <code>errorWaitTime</code> milliseconds
40-
* - multiple consecutive errors compound the unhealthy (cool down) period up to 8x the errorWaitTime
37+
* <ul>
38+
* <li>lower response index means the host is more likely to be used</li>
39+
* <li>should be based primarily on number of open connections to the host</li>
40+
* <li>an error will mark the host as unhealthy for <code>errorWaitTime</code> milliseconds</li>
41+
* <li>multiple consecutive errors compound the unhealthy (cool down) period up to 16x the errorWaitTime</li>
42+
* </ul>
4143
*/
4244
public class Host implements HostStats {
4345
private static final Logger l4j = Logger.getLogger(Host.class);
4446

4547
public static final int DEFAULT_ERROR_WAIT_MS = 1500;
4648
public static final int LOG_DELAY = 60000; // 1 minute
49+
public static final int MAX_COOL_DOWN_EXP = 4;
4750

4851
private String name;
4952
private boolean healthy = true;
@@ -74,8 +77,11 @@ public synchronized void connectionClosed() {
7477

7578
// Just in case our stats get out of whack somehow, make sure people know about it
7679
if (openConnections < 0) {
77-
if (System.currentTimeMillis() - lastLogTime > LOG_DELAY)
80+
long currentTime = System.currentTimeMillis();
81+
if (currentTime - lastLogTime > LOG_DELAY) {
7882
LogMF.warn(l4j, "openConnections for host %s is %d !", this, openConnections);
83+
lastLogTime = currentTime;
84+
}
7985
}
8086
}
8187

@@ -98,9 +104,9 @@ public boolean isHealthy() {
98104
if (!healthy) return false;
99105
else if (consecutiveErrors == 0) return true;
100106
else {
101-
long coolDownPower = consecutiveErrors > 3 ? 3 : consecutiveErrors - 1;
107+
long coolDownExp = consecutiveErrors > MAX_COOL_DOWN_EXP ? MAX_COOL_DOWN_EXP : consecutiveErrors - 1;
102108
long msSinceLastUse = System.currentTimeMillis() - lastConnectionTime;
103-
long errorCoolDown = (long) Math.pow(2, coolDownPower) * errorWaitTime;
109+
long errorCoolDown = (long) Math.pow(2, coolDownExp) * errorWaitTime;
104110
return msSinceLastUse > errorCoolDown;
105111
}
106112
}

src/main/java/com/emc/rest/smart/SmartClientFactory.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@
3636
import com.sun.jersey.core.impl.provider.entity.ByteArrayProvider;
3737
import com.sun.jersey.core.impl.provider.entity.FileProvider;
3838
import com.sun.jersey.core.impl.provider.entity.InputStreamProvider;
39-
import org.apache.http.impl.client.AbstractHttpClient;
40-
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
41-
import org.apache.http.impl.conn.PoolingClientConnectionManager;
4239
import org.apache.log4j.Logger;
4340

4441
public final class SmartClientFactory {
@@ -137,7 +134,7 @@ static ApacheHttpClient4Handler createApacheClientHandler(SmartConfig smartConfi
137134
ClientConfig clientConfig = new DefaultClientConfig();
138135

139136
// set up multi-threaded connection pool
140-
PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
137+
org.apache.http.impl.conn.PoolingClientConnectionManager connectionManager = new org.apache.http.impl.conn.PoolingClientConnectionManager();
141138
// 200 maximum active connections (should be more than enough for any JVM instance)
142139
connectionManager.setDefaultMaxPerRoute(200);
143140
connectionManager.setMaxTotal(200);
@@ -151,11 +148,18 @@ static ApacheHttpClient4Handler createApacheClientHandler(SmartConfig smartConfi
151148
if (smartConfig.getProxyPass() != null)
152149
clientConfig.getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_PASSWORD, smartConfig.getProxyPass());
153150

151+
// pass in jersey parameters from calling code (allows customization of client)
152+
for (String propName : smartConfig.getProperties().keySet()) {
153+
clientConfig.getProperties().put(propName, smartConfig.getProperty(propName));
154+
}
155+
154156
ApacheHttpClient4Handler handler = ApacheHttpClient4.create(clientConfig).getClientHandler();
155157

156158
// disable the retry handler if necessary
157-
if (smartConfig.getProperty(DISABLE_APACHE_RETRY) != null)
158-
((AbstractHttpClient) handler.getHttpClient()).setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
159+
if (smartConfig.getProperty(DISABLE_APACHE_RETRY) != null) {
160+
org.apache.http.impl.client.AbstractHttpClient httpClient = (org.apache.http.impl.client.AbstractHttpClient) handler.getHttpClient();
161+
httpClient.setHttpRequestRetryHandler(new org.apache.http.impl.client.DefaultHttpRequestRetryHandler(0, false));
162+
}
159163

160164
return handler;
161165
}

src/main/java/com/emc/rest/smart/SmartFilter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
import com.sun.jersey.api.client.ClientRequest;
3131
import com.sun.jersey.api.client.ClientResponse;
3232
import com.sun.jersey.api.client.filter.ClientFilter;
33-
import org.apache.http.HttpHost;
34-
import org.apache.http.client.utils.URIUtils;
3533

3634
import java.io.FilterInputStream;
3735
import java.io.IOException;
@@ -62,7 +60,8 @@ public ClientResponse handle(ClientRequest request) throws ClientHandlerExceptio
6260
// replace the host in the request
6361
URI uri = request.getURI();
6462
try {
65-
uri = URIUtils.rewriteURI(uri, new HttpHost(host.getName(), uri.getPort(), uri.getScheme()));
63+
org.apache.http.HttpHost httpHost = new org.apache.http.HttpHost(host.getName(), uri.getPort(), uri.getScheme());
64+
uri = org.apache.http.client.utils.URIUtils.rewriteURI(uri, httpHost);
6665
} catch (URISyntaxException e) {
6766
throw new RuntimeException("load-balanced host generated invalid URI", e);
6867
}

src/main/java/com/emc/rest/smart/ecs/EcsHostListProvider.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,18 @@ public List<Host> getHostList() {
9999
@Override
100100
public void runHealthCheck(Host host) {
101101
// header is workaround for STORAGE-1833
102-
client.resource(getRequestUri(host, "/?ping")).header("x-emc-namespace", "x").get(String.class);
102+
PingResponse response = client.resource(getRequestUri(host, "/?ping")).header("x-emc-namespace", "x")
103+
.get(PingResponse.class);
104+
105+
if (host instanceof VdcHost) {
106+
PingItem.Status status = PingItem.Status.OFF;
107+
if (response != null && response.getPingItemMap() != null) {
108+
PingItem pingItem = response.getPingItemMap().get(PingItem.MAINTENANCE_MODE);
109+
if (pingItem != null) status = pingItem.getStatus();
110+
}
111+
if (status == PingItem.Status.ON) ((VdcHost) host).setMaintenanceMode(true);
112+
else ((VdcHost) host).setMaintenanceMode(false);
113+
}
103114
}
104115

105116
@Override
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2015, EMC Corporation.
3+
* Redistribution and use in source and binary forms, with or without modification,
4+
* are permitted provided that the following conditions are met:
5+
*
6+
* + Redistributions of source code must retain the above copyright notice,
7+
* this list of conditions and the following disclaimer.
8+
* + Redistributions in binary form must reproduce the above copyright
9+
* notice, this list of conditions and the following disclaimer in the
10+
* documentation and/or other materials provided with the distribution.
11+
* + The name of EMC Corporation may not be used to endorse or promote
12+
* products derived from this software without specific prior written
13+
* permission.
14+
*
15+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
19+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25+
* POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
package com.emc.rest.smart.ecs;
28+
29+
import javax.xml.bind.annotation.XmlElement;
30+
import javax.xml.bind.annotation.XmlEnum;
31+
32+
public class PingItem {
33+
public static final String MAINTENANCE_MODE = "MAINTENANCE_MODE";
34+
35+
String name;
36+
Status status;
37+
String text;
38+
39+
@XmlElement(name = "Name")
40+
public String getName() {
41+
return name;
42+
}
43+
44+
public void setName(String name) {
45+
this.name = name;
46+
}
47+
48+
@XmlElement(name = "Status")
49+
public Status getStatus() {
50+
return status;
51+
}
52+
53+
public void setStatus(Status status) {
54+
this.status = status;
55+
}
56+
57+
@XmlElement(name = "Text")
58+
public String getText() {
59+
return text;
60+
}
61+
62+
public void setText(String text) {
63+
this.text = text;
64+
}
65+
66+
@XmlEnum
67+
public static enum Status {
68+
OFF, UNKNOWN, ON
69+
}
70+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2015, EMC Corporation.
3+
* Redistribution and use in source and binary forms, with or without modification,
4+
* are permitted provided that the following conditions are met:
5+
*
6+
* + Redistributions of source code must retain the above copyright notice,
7+
* this list of conditions and the following disclaimer.
8+
* + Redistributions in binary form must reproduce the above copyright
9+
* notice, this list of conditions and the following disclaimer in the
10+
* documentation and/or other materials provided with the distribution.
11+
* + The name of EMC Corporation may not be used to endorse or promote
12+
* products derived from this software without specific prior written
13+
* permission.
14+
*
15+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
19+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25+
* POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
package com.emc.rest.smart.ecs;
28+
29+
import javax.xml.bind.annotation.XmlElement;
30+
import javax.xml.bind.annotation.XmlRootElement;
31+
import javax.xml.bind.annotation.XmlTransient;
32+
import java.util.ArrayList;
33+
import java.util.HashMap;
34+
import java.util.List;
35+
import java.util.Map;
36+
37+
@XmlRootElement(name = "PingList")
38+
public class PingResponse {
39+
Map<String, PingItem> pingItemMap;
40+
41+
@XmlElement(name = "PingItem")
42+
public List<PingItem> getPingItems() {
43+
if (pingItemMap == null) return null;
44+
return new ArrayList<PingItem>(pingItemMap.values());
45+
}
46+
47+
public void setPingItems(List<PingItem> pingItems) {
48+
if (pingItems != null) {
49+
pingItemMap = new HashMap<String, PingItem>();
50+
for (PingItem item : pingItems) {
51+
pingItemMap.put(item.getName(), item);
52+
}
53+
}
54+
}
55+
56+
@XmlTransient
57+
public Map<String, PingItem> getPingItemMap() {
58+
return pingItemMap;
59+
}
60+
61+
public void setPingItemMap(Map<String, PingItem> pingItemMap) {
62+
this.pingItemMap = pingItemMap;
63+
}
64+
}

src/main/java/com/emc/rest/smart/ecs/VdcHost.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@
3030

3131
public class VdcHost extends Host {
3232
private Vdc vdc;
33+
private boolean maintenanceMode;
3334

3435
public VdcHost(Vdc vdc, String name) {
3536
super(name);
3637
this.vdc = vdc;
3738
}
3839

40+
@Override
41+
public boolean isHealthy() {
42+
return !isMaintenanceMode() && super.isHealthy();
43+
}
44+
3945
@Override
4046
public boolean equals(Object o) {
4147
if (this == o) return true;
@@ -63,4 +69,12 @@ public String toString() {
6369
public Vdc getVdc() {
6470
return vdc;
6571
}
72+
73+
public boolean isMaintenanceMode() {
74+
return maintenanceMode;
75+
}
76+
77+
public void setMaintenanceMode(boolean maintenanceMode) {
78+
this.maintenanceMode = maintenanceMode;
79+
}
6680
}

src/test/java/com/emc/rest/smart/HostTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,27 @@ public void testHost() throws Exception {
121121
Assert.assertEquals(0, host.getResponseIndex());
122122
Assert.assertTrue(host.isHealthy());
123123
}
124+
125+
@Test
126+
public void testErrorWaitLimit() throws Exception {
127+
Host host = new Host("bar");
128+
host.setErrorWaitTime(100); // don't want this test to take forever
129+
130+
Assert.assertTrue(host.isHealthy());
131+
132+
// 8 consecutive errors
133+
long errors = 8;
134+
for (int i = 0; i < errors; i++) {
135+
host.connectionOpened();
136+
host.callComplete(true);
137+
host.connectionClosed();
138+
}
139+
140+
Assert.assertEquals(errors, host.getConsecutiveErrors());
141+
long maxCoolDownMs = host.getErrorWaitTime() * (long) Math.pow(2, Host.MAX_COOL_DOWN_EXP) + 10; // add a few ms
142+
143+
Thread.sleep(maxCoolDownMs);
144+
145+
Assert.assertTrue(host.isHealthy());
146+
}
124147
}

src/test/java/com/emc/rest/smart/SmartClientTest.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@
2828

2929
import com.emc.util.TestConfig;
3030
import com.sun.jersey.api.client.Client;
31+
import com.sun.jersey.api.client.ClientHandlerException;
3132
import com.sun.jersey.api.client.ClientResponse;
3233
import com.sun.jersey.api.client.WebResource;
34+
import com.sun.jersey.client.apache4.config.ApacheHttpClient4Config;
3335
import org.apache.commons.codec.binary.Base64;
36+
import org.apache.http.params.BasicHttpParams;
37+
import org.apache.http.params.HttpConnectionParams;
38+
import org.apache.http.params.HttpParams;
3439
import org.apache.log4j.Logger;
3540
import org.junit.Assert;
3641
import org.junit.Assume;
@@ -42,9 +47,7 @@
4247
import java.text.DateFormat;
4348
import java.text.SimpleDateFormat;
4449
import java.util.*;
45-
import java.util.concurrent.ExecutorService;
46-
import java.util.concurrent.Executors;
47-
import java.util.concurrent.Future;
50+
import java.util.concurrent.*;
4851
import java.util.concurrent.atomic.AtomicInteger;
4952

5053
public class SmartClientTest {
@@ -105,6 +108,36 @@ public void run() {
105108
Assert.assertEquals("at least one task failed", 100, successCount.intValue());
106109
}
107110

111+
@Test
112+
public void testConnTimeout() throws Exception {
113+
int CONNECTION_TIMEOUT_MILLIS = 10000; // 10 seconds
114+
115+
HttpParams httpParams = new BasicHttpParams();
116+
HttpConnectionParams.setConnectionTimeout(httpParams, CONNECTION_TIMEOUT_MILLIS);
117+
118+
SmartConfig smartConfig = new SmartConfig("10.4.4.180");
119+
smartConfig.setProperty(ApacheHttpClient4Config.PROPERTY_HTTP_PARAMS, httpParams);
120+
121+
final Client client = SmartClientFactory.createStandardClient(smartConfig);
122+
123+
Future future = Executors.newSingleThreadExecutor().submit(new Runnable() {
124+
@Override
125+
public void run() {
126+
client.resource("http://10.4.4.180:9020/?ping").get(String.class);
127+
Assert.fail("response was not expected; choose an IP that is not in use");
128+
}
129+
});
130+
131+
try {
132+
future.get(CONNECTION_TIMEOUT_MILLIS + 1000, TimeUnit.MILLISECONDS); // give an extra second leeway
133+
} catch (TimeoutException e) {
134+
Assert.fail("connection did not timeout");
135+
} catch (ExecutionException e) {
136+
Assert.assertTrue(e.getCause() instanceof ClientHandlerException);
137+
Assert.assertTrue(e.getMessage().contains("timed out"));
138+
}
139+
}
140+
108141
private void getServiceInfo(Client client, URI serverUri, String uid, String secretKey) {
109142
String path = "/rest/service";
110143
String date = getDateFormat().format(new Date());

0 commit comments

Comments
 (0)