package io.searchbox.client;
import io.searchbox.client.config.HttpClientConfig;
import io.searchbox.client.http.JestHttpClient;
import io.searchbox.cluster.Health;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.http.HttpTransportSettings;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.transport.Netty4Plugin;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* @author cihat keser
*/
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
public class JestClientFactoryIntegrationTest extends ESIntegTestCase {
JestClientFactory factory = new JestClientFactory();
@Test
public void testDiscovery() throws InterruptedException, IOException {
// wait for 4 active nodes
internalCluster().ensureAtLeastNumDataNodes(4);
assertEquals("All nodes in cluster should have HTTP endpoint exposed", 4, cluster().httpAddresses().length);
factory.setHttpClientConfig(new HttpClientConfig
.Builder("http://localhost:" + cluster().httpAddresses()[0].getPort())
.discoveryEnabled(true)
.discoveryFrequency(500l, TimeUnit.MILLISECONDS)
.build());
JestHttpClient jestClient = (JestHttpClient) factory.getObject();
assertNotNull(jestClient);
// wait for NodeChecker to do the discovery
Thread.sleep(3000);
assertEquals(
"All 4 nodes should be discovered and be in the client's server list",
4,
jestClient.getServerPoolSize()
);
internalCluster().ensureAtMostNumDataNodes(3);
int numServers = 0;
int retries = 0;
while (numServers != 3 && retries < 30) {
numServers = jestClient.getServerPoolSize();
retries++;
Thread.sleep(1000);
}
assertEquals(
"Only 3 nodes should be in Jest's list",
3,
jestClient.getServerPoolSize()
);
jestClient.shutdownClient();
}
@Test
public void testDiscoveryWithFiltering() throws InterruptedException, IOException {
// wait for 3 active nodes
internalCluster().ensureAtLeastNumDataNodes(3);
// spin up two more client nodes with additional attributes
Settings settings = Settings.builder().put(internalCluster().getDefaultSettings())
.put("node.master", false) // for example, a client node
.put("node.data", false)
.put("node.attr.type", "aardvark") // put some arbitrary attribute to filter by
.build();
String clientNode1 = internalCluster().startNode(settings);
String clientNode2 = internalCluster().startNode(settings);
assertNotEquals("client nodes should be different", clientNode1, clientNode2);
assertEquals("All nodes in cluster should have HTTP endpoint exposed", 5, cluster().httpAddresses().length);
factory.setHttpClientConfig(new HttpClientConfig
.Builder("http://localhost:" + cluster().httpAddresses()[0].getPort())
.discoveryEnabled(true)
.discoveryFilter("type:aardvark")
.discoveryFrequency(500l, TimeUnit.MILLISECONDS)
.build());
JestHttpClient jestClient = (JestHttpClient) factory.getObject();
assertNotNull(jestClient);
// wait for NodeChecker to do the discovery
Thread.sleep(3000);
assertEquals(
"Only 2 nodes should be discovered and be in the client's server list",
2,
jestClient.getServerPoolSize()
);
jestClient.shutdownClient();
}
@Test
public void testIdleConnectionReaper() throws Exception {
internalCluster().ensureAtLeastNumDataNodes(3);
assertEquals("All nodes in cluster should have HTTP endpoint exposed", 3, cluster().httpAddresses().length);
factory.setHttpClientConfig(new HttpClientConfig
.Builder("http://localhost:" + cluster().httpAddresses()[0].getPort())
.multiThreaded(true)
.discoveryEnabled(true)
.discoveryFrequency(100l, TimeUnit.MILLISECONDS)
.maxConnectionIdleTime(1500L, TimeUnit.MILLISECONDS)
.maxTotalConnection(75)
.defaultMaxTotalConnectionPerRoute(75)
.build());
JestHttpClient jestClient = (JestHttpClient) factory.getObject();
assertNotNull(jestClient);
Thread.sleep(300L); // Allow nodechecker to do it's thing and use at least one connection in the pool
// Ask for the cluster health just to use some connections
int maxPoolSize = getPoolSize(jestClient);
for (int x = 0; x < 5; ++x) {
jestClient.execute(new Health.Builder().build());
maxPoolSize = Math.max(maxPoolSize, getPoolSize(jestClient));
}
Thread.sleep(3200); // Allow cxn reaper a chance to do it's thing
int newPoolSize = getPoolSize(jestClient);
// The new pool size should be much less than the maxPoolSize since the idle connection reaper will have run
// twice in the time between maxPoolSize's last calculation and now. There should really only be 1-2 connections
// in the pool at this point since our idle timeout is set so low for this test.
assertTrue(maxPoolSize > newPoolSize);
jestClient.shutdownClient();
}
@Test
public void testNoIdleConnectionReaper() throws Exception {
internalCluster().ensureAtLeastNumDataNodes(3);
assertEquals("All nodes in cluster should have HTTP endpoint exposed", 3, cluster().httpAddresses().length);
factory.setHttpClientConfig(new HttpClientConfig
.Builder("http://localhost:" + cluster().httpAddresses()[0].getPort())
.multiThreaded(true)
.discoveryEnabled(true)
.discoveryFrequency(100l, TimeUnit.MILLISECONDS)
.maxTotalConnection(75)
.defaultMaxTotalConnectionPerRoute(75)
.build());
JestHttpClient jestClient = (JestHttpClient) factory.getObject();
assertNotNull(jestClient);
Thread.sleep(300L); // Allow nodechecker to do it's thing and use at least one connection in the pool
// Ask for the cluster health just to use some connections and create a little white noise
int maxPoolSize = getPoolSize(jestClient);
for (int x = 0; x < 5; ++x) {
jestClient.execute(new Health.Builder().build());
maxPoolSize = Math.max(maxPoolSize, getPoolSize(jestClient));
}
Thread.sleep(3000); // Allow for a quiesce period of no activity (except for nodechecker)
int newPoolSize = getPoolSize(jestClient);
// These two values being equal proves that connections returned to the pool stick around for some non-zero
// duration of time while they wait to be re-leased. It's impractical to prove in an integration test that they
// can in fact stay around for over an hour without ever being used (by which time the server has most certainly
// closed the connection).
assertEquals(maxPoolSize, newPoolSize);
jestClient.shutdownClient();
}
/**
* Forgive me these sins. This is the only way I can think of to determine the *actual* size of the connection pool
* without wrapping large quantities of the underlying client.
* <p>
* This whole method is cheating and full of bad examples. Don't copy this. You've been warned.
*/
private int getPoolSize(JestHttpClient client) throws Exception {
try {
Field fieldHttpClient = client.getClass().getDeclaredField("httpClient");
fieldHttpClient.setAccessible(true);
Object objInternalHttpClient = fieldHttpClient.get(client);
Field fieldConnectionManager = objInternalHttpClient.getClass().getDeclaredField("connManager");
fieldConnectionManager.setAccessible(true);
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = (PoolingHttpClientConnectionManager) fieldConnectionManager.get(objInternalHttpClient);
PoolStats poolStats = poolingHttpClientConnectionManager.getTotalStats();
return poolStats.getAvailable() + poolStats.getLeased();
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.builder().put(super.nodeSettings(nodeOrdinal))
.put(NetworkModule.HTTP_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
.put(NetworkModule.HTTP_ENABLED.getKey(), true)
.build();
}
@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return Collections.singletonList(Netty4Plugin.class);
}
}