package org.infinispan.server.test.client.hotrod;
import static org.infinispan.server.test.util.ITestUtils.isDistributedMode;
import static org.infinispan.server.test.util.ITestUtils.isLocalMode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Field;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.infinispan.arquillian.core.RemoteInfinispanServer;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.Configuration;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.configuration.ServerConfiguration;
import org.infinispan.client.hotrod.impl.ConfigurationProperties;
import org.infinispan.client.hotrod.impl.RemoteCacheImpl;
import org.infinispan.client.hotrod.impl.consistenthash.ConsistentHash;
import org.infinispan.client.hotrod.impl.operations.OperationsFactory;
import org.infinispan.client.hotrod.impl.transport.TransportFactory;
import org.infinispan.client.hotrod.impl.transport.tcp.FailoverRequestBalancingStrategy;
import org.infinispan.client.hotrod.impl.transport.tcp.TcpTransport;
import org.infinispan.client.hotrod.impl.transport.tcp.TcpTransportFactory;
import org.infinispan.client.hotrod.logging.Log;
import org.infinispan.client.hotrod.logging.LogFactory;
import org.infinispan.commons.marshall.Marshaller;
import org.junit.Test;
/**
* Tests for HotRod client and its RemoteCacheManager API. Subclasses must provide
* a way to get the list of remote HotRod servers.
* <p/>
* Subclasses may be used in Client-Server mode or Hybrid mode where HotRod server
* runs as a library deployed in an application server.
*
* @author Richard Achmatowicz
* @author Martin Gencur
* @author Jozef Vilkolak
*/
public abstract class AbstractRemoteCacheManagerIT {
private static final String IPV6_REGEX = "\\A\\[(.*)\\]:([0-9]+)\\z";
private static final String IPV4_REGEX = "\\A([^:]+):([0-9]+)\\z";
protected static String testCache = "default";
private static final Log log = LogFactory.getLog(AbstractRemoteCacheManagerIT.class);
protected abstract List<RemoteInfinispanServer> getServers();
// creates a configuration with the same values as the hotrod-client.properties files, in ISPN 6.X.Y hotrod-client.properties file will be dropped
private ConfigurationBuilder createRemoteCacheManagerConfigurationBuilder() {
ConfigurationBuilder config = new ConfigurationBuilder();
addServers(config);
config.balancingStrategy("org.infinispan.server.test.client.hotrod.HotRodTestRequestBalancingStrategy")
.forceReturnValues(true)
.tcpNoDelay(false)
.tcpKeepAlive(true)
.transportFactory("org.infinispan.server.test.client.hotrod.HotRodTestTransportFactory")
.marshaller("org.infinispan.server.test.client.hotrod.HotRodTestMarshaller")
.asyncExecutorFactory().factoryClass("org.infinispan.server.test.client.hotrod.HotRodTestExecutorFactory")
.addExecutorProperty("infinispan.client.hotrod.default_executor_factory.pool_size", "20")
.addExecutorProperty("infinispan.client.hotrod.default_executor_factory.queue_size", "200000")
.keySizeEstimate(128)
.valueSizeEstimate(1024);
return config;
}
/*
* Tests the constructor RemoteCacheManager() - the properties file hotrod-client.properties from classpath is used to
* define properties - confirm that the file hotrod-client.properties is picked up
*/
@Test
public void testDefaultConstructor() throws Exception {
Configuration conf = createRemoteCacheManagerConfigurationBuilder().build();
// use the properties file hotrod-client.properties on classpath
// this properties file contains the test properties with server_list set to ${node0.address}:11222;${node1.address}:11222
RemoteCacheManager rcm = new RemoteCacheManager();
RemoteCacheManager rcm2 = new RemoteCacheManager(false);
assertTrue(rcm.isStarted());
assertFalse(rcm2.isStarted());
RemoteCache rc = rcm.getCache(testCache);
assertEqualConfiguration(conf, rc);
}
@Test
public void testConfigurationConstructors() throws Exception {
Configuration conf = createRemoteCacheManagerConfigurationBuilder().build();
RemoteCacheManager rcm = new RemoteCacheManager(conf);
RemoteCacheManager rcm2 = new RemoteCacheManager(conf, false);
assertTrue(rcm.isStarted());
assertFalse(rcm2.isStarted());
RemoteCache rc = rcm.getCache(testCache);
assertEqualConfiguration(conf, rc);
}
@Test
public void testEmptyConfiguration() throws Exception {
ConfigurationBuilder confBuilder = new ConfigurationBuilder();
addServers(confBuilder);
RemoteCacheManager rcm = new RemoteCacheManager(confBuilder.build());
RemoteCache rc = rcm.getCache(testCache);
ConfigurationBuilder builder = new ConfigurationBuilder();
addServers(builder);
builder.balancingStrategy("org.infinispan.client.hotrod.impl.transport.tcp.RoundRobinBalancingStrategy")
.forceReturnValues(false)
.tcpNoDelay(true)
.transportFactory("org.infinispan.client.hotrod.impl.transport.tcp.TcpTransportFactory")
.marshaller("org.infinispan.commons.marshall.jboss.GenericJBossMarshaller")
.asyncExecutorFactory().factoryClass("org.infinispan.client.hotrod.impl.async.DefaultAsyncExecutorFactory")
.addExecutorProperty("infinispan.client.hotrod.default_executor_factory.pool_size", "10")
.addExecutorProperty("infinispan.client.hotrod.default_executor_factory.queue_size", "100000")
.keySizeEstimate(64)
.valueSizeEstimate(512);
Configuration defaultConf = builder.build();
assertEqualConfiguration(defaultConf, rc);
}
private void addServers(ConfigurationBuilder builder) {
for (RemoteInfinispanServer server : getServers()) {
builder.addServer().host(server.getHotrodEndpoint().getInetAddress().getHostName())
.port(server.getHotrodEndpoint().getPort());
}
}
@Test
public void testStartStop() {
Configuration cfg = createRemoteCacheManagerConfigurationBuilder().build();
RemoteCacheManager rcm = new RemoteCacheManager(cfg, false);
// check initial status
assertTrue("RemoteCacheManager should not be started initially", !rcm.isStarted());
// check start status
rcm.start();
assertTrue("RemoteCacheManager should be started after calling start()", rcm.isStarted());
// check stopped status
rcm.stop();
assertTrue("RemoteCacheManager should be stopped after calling stop()", !rcm.isStarted());
}
@Test
public void testGetNonExistentCache() {
// When get named cache which doesn't exists it is created new with default settings
// but it is not able to get some stats because it is not configured properly
RemoteCacheManager rcm = new RemoteCacheManager(createRemoteCacheManagerConfigurationBuilder().build());
RemoteCache rc1 = rcm.getCache("nonExistentCache");
try {
for (String stat : rc1.stats().getStatsMap().keySet()) {
log.tracef(stat + " " + rc1.stats().getStatsMap().get(stat));
}
fail("Should throw CacheNotFoundException");
} catch (Exception e) {
//ok
}
}
/*
* Tests the load balancing feature
*
* Checks that the default load balancing strategy, RoundRobin, will cycle through the server list as operations are
* executed.
*
* For each operation executed, we would need to obtain its Transport and call getServerAddress() to discover which address
* was used, but this is difficult to arrange. So instead, we simulate by making repeated calls to
* TransportFactory.getTransport()
*/
@Test
public void testDefaultLoadBalancing() throws Exception {
if (!isLocalMode()) {
doTestDefaultLoadBalanding();
}
}
private void doTestDefaultLoadBalanding() throws Exception {
InetSocketAddress hostport0 = new InetSocketAddress(getServers().get(0).getHotrodEndpoint().getInetAddress().getHostName(), getServers().get(0)
.getHotrodEndpoint().getPort());
InetSocketAddress hostport1 = new InetSocketAddress(getServers().get(1).getHotrodEndpoint().getInetAddress().getHostName(), getServers().get(1)
.getHotrodEndpoint().getPort());
TcpTransport tt = null;
InetSocketAddress sock_addr = null;
StringBuilder serverAddrSequence = new StringBuilder();
String hostport0String = hostport0.getAddress().getHostAddress() + ":" + hostport0.getPort();
String hostport1String = hostport1.getAddress().getHostAddress() + ":" + hostport1.getPort();
String expectedSequence1 = hostport0String + " " + hostport1String + " " + hostport0String;
String expectedSequence2 = hostport1String + " " + hostport0String + " " + hostport1String;
String expectedSequenceLocalMode = hostport0String + " " + hostport0String + " " + hostport0String;
Configuration cfg = createRemoteCacheManagerConfigurationBuilder().build();
RemoteCacheManager rcm = new RemoteCacheManager(cfg);
RemoteCache rc = rcm.getCache(testCache);
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
// the factory used to create all remote operations for this class
OperationsFactory of = getOperationsFactoryField(rci);
TcpTransportFactory ttf = (TcpTransportFactory) getTransportFactoryField(of);
// perform first simulated operation
tt = (TcpTransport) ttf.getTransport(null, rci.getName().getBytes());
sock_addr = (InetSocketAddress) tt.getServerAddress();
ttf.releaseTransport(tt);
serverAddrSequence.append(sock_addr.getAddress().getHostAddress() + ":" + sock_addr.getPort()).append(" ");
tt = (TcpTransport) ttf.getTransport(null, rci.getName().getBytes());
sock_addr = (InetSocketAddress) tt.getServerAddress();
ttf.releaseTransport(tt);
serverAddrSequence.append(sock_addr.getAddress().getHostAddress() + ":" + sock_addr.getPort()).append(" ");
tt = (TcpTransport) ttf.getTransport(null, rci.getName().getBytes());
sock_addr = (InetSocketAddress) tt.getServerAddress();
ttf.releaseTransport(tt);
serverAddrSequence.append(sock_addr.getAddress().getHostAddress() + ":" + sock_addr.getPort());
if (!isLocalMode()) {
assertTrue(
"loadbalancing server sequence expected either " + expectedSequence1 + " or " + expectedSequence2
+ ", actual sequence: " + serverAddrSequence.toString(),
serverAddrSequence.toString().equals(expectedSequence1)
|| serverAddrSequence.toString().equals(expectedSequence2));
} else {
assertEquals("LOCAL mode - loadbalancing server sequence expected " + expectedSequenceLocalMode
+ ", actual sequence: " + serverAddrSequence.toString(),
serverAddrSequence.toString(), expectedSequenceLocalMode);
}
}
/*
* Tests the load balancing feature
*
* Checks that a custom load balancing strategy, Node0Only, will cycle through the server list as operations are executed.
*
* NOTE: the default properties have a server list of node0/node1.
*/
@Test
public void testCustomLoadBalancing() throws Exception {
if (!isLocalMode()) {
doTestCustomLoadBalancing();
}
}
private void doTestCustomLoadBalancing() throws Exception {
// the InetSocketAddress instances which this test should be using
InetSocketAddress hostport0 = new InetSocketAddress(getServers().get(0).getHotrodEndpoint().getInetAddress().getHostName(), getServers().get(0)
.getHotrodEndpoint().getPort());
InetSocketAddress hostport1 = new InetSocketAddress(getServers().get(1).getHotrodEndpoint().getInetAddress().getHostName(), getServers().get(1)
.getHotrodEndpoint().getPort());
TcpTransport tt = null;
InetSocketAddress sock_addr = null;
// create configuration with the custom balancing strategy
Configuration cfg = createRemoteCacheManagerConfigurationBuilder()
.balancingStrategy("org.infinispan.server.test.client.hotrod.Node0OnlyBalancingStrategy")
.build();
RemoteCacheManager rcm = new RemoteCacheManager(cfg);
RemoteCache rc = rcm.getCache(testCache);
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
// the factory used to create all remote operations for this class
OperationsFactory of = getOperationsFactoryField(rci);
TcpTransportFactory ttf = (TcpTransportFactory) getTransportFactoryField(of);
// perform first simulated operation
tt = (TcpTransport) ttf.getTransport(null, rci.getName().getBytes());
sock_addr = (InetSocketAddress) tt.getServerAddress();
ttf.releaseTransport(tt);
assertEquals("load balancing first request: server address expected " + hostport0 + ", actual server address "
+ sock_addr, sock_addr, hostport0);
tt = (TcpTransport) ttf.getTransport(null, rci.getName().getBytes());
sock_addr = (InetSocketAddress) tt.getServerAddress();
ttf.releaseTransport(tt);
assertEquals("load balancing second request: server address expected " + hostport0 + ", actual server address"
+ sock_addr, sock_addr, hostport0);
}
private void assertEqualConfiguration(Configuration config, RemoteCache rc) throws Exception {
assertEquals(config.balancingStrategyClass().getName(), getRequestBalancingStrategyProperty(rc));
assertNull(config.balancingStrategy());
// Configuration stores servers as List<ServerConfiguration>, getServerListProperty returns string "host1:port1;host2:port2..."
String servers = getServerListProperty(rc);
for (ServerConfiguration scfg : config.servers()) {
boolean found = false;
String host;
int port = ConfigurationProperties.DEFAULT_HOTROD_PORT;
Pattern patternIpv6 = Pattern.compile(IPV6_REGEX);
Pattern patternIpv4 = Pattern.compile(IPV4_REGEX);
for (String server : servers.split(";")) {
Matcher matcher6 = patternIpv6.matcher(server);
Matcher matcher4 = patternIpv4.matcher(server);
if (matcher6.matches()) {
host = matcher6.group(1);
port = Integer.parseInt(matcher6.group(2));
} else if (matcher4.matches()) {
host = matcher4.group(1);
port = Integer.parseInt(matcher4.group(2));
} else {
host = server;
}
if (scfg.host().equals(host) && scfg.port() == port)
found = true;
}
if (!found)
fail("The remote cache manager was configured to have server with an address " + scfg.host() + ":" + scfg.port() + ", but it doesn't.");
}
assertEquals(config.forceReturnValues(), Boolean.parseBoolean(getForceReturnValueProperty(rc)));
assertEquals(config.tcpNoDelay(), Boolean.parseBoolean(getTcpNoDelayProperty(rc)));
assertEquals(config.tcpKeepAlive(), Boolean.parseBoolean(getTcpKeepAliveProperty(rc)));
assertEquals(config.maxRetries(), Integer.parseInt(getMaxRetries(rc)));
// asyncExecutorFactory compared only with the configuration itself
assertEquals(config.asyncExecutorFactory().factoryClass().getName(),
rc.getRemoteCacheManager().getConfiguration().asyncExecutorFactory().factoryClass().getName());
assertEquals(config.transportFactory().getName(), getTransportFactoryProperty(rc));
// either marshaller or marshallerClass is set
if (config.marshaller() != null) {
assertEquals(config.marshaller().getClass().getName(), getMarshallerProperty(rc));
} else {
assertEquals(config.marshallerClass().getName(), getMarshallerProperty(rc));
}
// need to do some hotrod operation, only then is the hash initialized
rc.stats();
// get hash function only for distribution mode
if (isDistributedMode()) {
assertEquals(config.consistentHashImpl(3).getName(), getHashFunctionImplProperty(rc));
}
assertEquals(config.keySizeEstimate(), getKeySizeEstimateProperty(rc));
assertEquals(config.valueSizeEstimate(), getValueSizeEstimateProperty(rc));
}
private String getRequestBalancingStrategyProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
TcpTransportFactory ttf = (TcpTransportFactory) getTransportFactoryField(of);
FailoverRequestBalancingStrategy rbs = ttf.getBalancer(RemoteCacheManager.cacheNameBytes());
return rbs.getClass().getName();
}
private String getServerListProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
TcpTransportFactory ttf = (TcpTransportFactory) getTransportFactoryField(of);
Collection<SocketAddress> servers = ttf.getServers();
// create a list of IP address:port to return
StringBuilder serverList = new StringBuilder();
int listSize = servers.size();
int i = 0;
for (Iterator iter = servers.iterator(); iter.hasNext(); i++) {
InetSocketAddress addr = (InetSocketAddress) iter.next();
// take care to remove prepended backslash
String serverAddress = addr.getAddress().toString();
// if (serverAddress.startsWith("/"))
// serverAddress = serverAddress.substring(1);
// serverList.append(serverAddress);
if (addr.getAddress() instanceof Inet6Address) {
serverList.append('[').append(addr.getHostName()).append(']');
} else {
serverList.append(addr.getHostName());
}
serverList.append(":");
serverList.append(addr.getPort());
if (i < listSize - 1)
serverList.append(";");
}
return serverList.toString();
}
private String getForceReturnValueProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
boolean forceReturn = getForceReturnValueField(of);
return Boolean.toString(forceReturn);
}
private String getTcpNoDelayProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
TcpTransportFactory ttf = (TcpTransportFactory) getTransportFactoryField(of);
boolean tcpNoDelay = ttf.isTcpNoDelay();
return Boolean.toString(tcpNoDelay);
}
private String getTcpKeepAliveProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
TcpTransportFactory ttf = (TcpTransportFactory) getTransportFactoryField(of);
boolean tcpKeepAlive = ttf.isTcpKeepAlive();
return Boolean.toString(tcpKeepAlive);
}
private String getMaxRetries(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
TransportFactory ttf = getTransportFactoryField(of);
return Integer.toString(ttf.getMaxRetries());
}
private String getTransportFactoryProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
TransportFactory tf = getTransportFactoryField(of);
// need to check instance type
return tf.getClass().getName();
}
private String getMarshallerProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
Marshaller m = getMarshallerField(rci);
// need to check instance
return m.getClass().getName();
}
private String getHashFunctionImplProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
OperationsFactory of = getOperationsFactoryField(rci);
TcpTransportFactory ttf = (TcpTransportFactory) getTransportFactoryField(of);
ConsistentHash ch = ttf.getConsistentHash(((RemoteCacheImpl) rc).getName().getBytes());
return ch.getClass().getName();
}
private int getKeySizeEstimateProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
return getEstimateKeySizeField(rci);
}
private int getValueSizeEstimateProperty(RemoteCache rc) throws Exception {
RemoteCacheImpl rci = (RemoteCacheImpl) rc;
return getEstimateValueSizeField(rci);
}
private OperationsFactory getOperationsFactoryField(RemoteCacheImpl rci) throws Exception {
Field field = null;
try {
field = RemoteCacheImpl.class.getDeclaredField("operationsFactory");
} catch (NoSuchFieldException e) {
throw new Exception("Could not access operationsFactory field", e);
}
field.setAccessible(true);
OperationsFactory fieldValue = null;
try {
fieldValue = (OperationsFactory) field.get(rci);
} catch (IllegalAccessException e) {
throw new Exception("Could not access OperationsFactory field", e);
}
return fieldValue;
}
private int getEstimateKeySizeField(RemoteCacheImpl rci) throws Exception {
Field field = null;
try {
field = RemoteCacheImpl.class.getDeclaredField("estimateKeySize");
} catch (NoSuchFieldException e) {
throw new Exception("Could not access estimateKeySize field", e);
}
field.setAccessible(true);
int fieldValue = 0;
try {
fieldValue = field.getInt(rci);
} catch (IllegalAccessException e) {
throw new Exception("Could not access estimateKeySize field", e);
}
return fieldValue;
}
private int getEstimateValueSizeField(RemoteCacheImpl rci) throws Exception {
Field field = null;
try {
field = RemoteCacheImpl.class.getDeclaredField("estimateValueSize");
} catch (NoSuchFieldException e) {
throw new Exception("Could not access estimateValueSize field", e);
}
field.setAccessible(true);
int fieldValue = 0;
try {
fieldValue = field.getInt(rci);
} catch (IllegalAccessException e) {
throw new Exception("Could not access estimateValueSize field", e);
}
return fieldValue;
}
private Marshaller getMarshallerField(RemoteCacheImpl rci) throws Exception {
Field field = null;
try {
field = RemoteCacheImpl.class.getDeclaredField("marshaller");
} catch (NoSuchFieldException e) {
throw new Exception("Could not access marshaller field", e);
}
field.setAccessible(true);
Marshaller fieldValue = null;
try {
fieldValue = (Marshaller) field.get(rci);
} catch (IllegalAccessException e) {
throw new Exception("Could not access marshaller field", e);
}
return fieldValue;
}
private boolean getForceReturnValueField(OperationsFactory of) throws Exception {
Field field = null;
try {
field = OperationsFactory.class.getDeclaredField("forceReturnValue");
} catch (NoSuchFieldException e) {
throw new Exception("Could not access forceReturnValue field", e);
}
field.setAccessible(true);
boolean fieldValue = false;
try {
fieldValue = field.getBoolean(of);
} catch (IllegalAccessException e) {
throw new Exception("Could not access forceReturnValue field", e);
}
return fieldValue;
}
private TransportFactory getTransportFactoryField(OperationsFactory of) throws Exception {
Field field = null;
try {
field = OperationsFactory.class.getDeclaredField("transportFactory");
} catch (NoSuchFieldException e) {
throw new Exception("Could not access transportFactory field", e);
}
field.setAccessible(true);
TransportFactory fieldValue = null;
try {
fieldValue = (TransportFactory) field.get(of);
} catch (IllegalAccessException e) {
throw new Exception("Could not access transportFactory field", e);
}
return fieldValue;
}
}