package org.infinispan.client.hotrod.test; import static org.infinispan.distribution.DistributionTestHelper.isFirstOwner; import java.io.IOException; import java.io.InputStream; import java.net.BindException; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.infinispan.Cache; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; import org.infinispan.client.hotrod.event.RemoteCacheSupplier; import org.infinispan.client.hotrod.impl.protocol.HotRodConstants; import org.infinispan.client.hotrod.impl.transport.tcp.FailoverRequestBalancingStrategy; import org.infinispan.client.hotrod.impl.transport.tcp.TcpTransportFactory; import org.infinispan.client.hotrod.logging.Log; import org.infinispan.commons.api.BasicCache; import org.infinispan.commons.marshall.jboss.GenericJBossMarshaller; import org.infinispan.commons.util.Util; import org.infinispan.container.versioning.NumericVersion; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.metadata.Metadata; import org.infinispan.scripting.ScriptingManager; import org.infinispan.server.hotrod.HotRodServer; import org.infinispan.server.hotrod.configuration.HotRodServerConfigurationBuilder; import org.infinispan.server.hotrod.test.HotRodTestingUtil; import org.infinispan.test.TestingUtil; import org.infinispan.util.logging.LogFactory; import io.netty.channel.ChannelException; import io.netty.channel.unix.Errors.NativeIoException; /** * Utility methods for the Hot Rod client * * @author Galder ZamarreƱo * @since 5.1 */ public class HotRodClientTestingUtil { private static final Log log = LogFactory.getLog(HotRodClientTestingUtil.class, Log.class); /** * This needs to be different than the one used in the server tests in order to make sure that there's no clash. */ private static final AtomicInteger uniquePort = new AtomicInteger(15232); public static HotRodServer startHotRodServer(EmbeddedCacheManager cacheManager, HotRodServerConfigurationBuilder builder) { return startHotRodServer(cacheManager, uniquePort.incrementAndGet(), builder); } private static boolean isBindException(Throwable e) { if (e instanceof BindException) return true; if (e instanceof NativeIoException) { NativeIoException nativeIoException = (NativeIoException) e; return nativeIoException.getMessage().contains("bind"); } return false; } public static HotRodServer startHotRodServer(EmbeddedCacheManager cacheManager, int startPort, HotRodServerConfigurationBuilder builder) { // TODO: This is very rudimentary!! HotRodTestingUtil needs a more robust solution where ports are generated randomly and retries if already bound HotRodServer server = null; int maxTries = 10; int currentTries = 0; Throwable lastError = null; int port = startPort; while (server == null && currentTries < maxTries) { try { server = HotRodTestingUtil.startHotRodServer(cacheManager, port++, builder); } catch (ChannelException e) { if (!isBindException(e.getCause())) { throw e; } else { log.debug("Address already in use: [" + e.getMessage() + "], so let's try next port"); currentTries++; lastError = e; } } catch (Throwable t) { if (!isBindException(t)) { throw t; } else { log.debug("Address already in use: [" + t.getMessage() + "], so let's try next port"); currentTries++; lastError = t; } } } if (server == null && lastError != null) throw new AssertionError(lastError); return server; } public static HotRodServer startHotRodServer(EmbeddedCacheManager cacheManager) { return startHotRodServer(cacheManager, new HotRodServerConfigurationBuilder()); } /** * Kills a remote cache manager. * * @param rcm the remote cache manager instance to kill */ public static void killRemoteCacheManager(RemoteCacheManager rcm) { try { if (rcm != null) rcm.stop(); } catch (Throwable t) { log.warn("Error stopping remote cache manager", t); } } /** * Kills a group of remote cache managers. * * @param rcm * the remote cache manager instances to kill */ public static void killRemoteCacheManagers(RemoteCacheManager... rcms) { if (rcms != null) { for (RemoteCacheManager rcm : rcms) { try { if (rcm != null) rcm.stop(); } catch (Throwable t) { log.warn("Error stopping remote cache manager", t); } } } } /** * Kills a group of Hot Rod servers. * * @param servers the group of Hot Rod servers to kill */ public static void killServers(HotRodServer... servers) { if (servers != null) { for (HotRodServer server : servers) { try { if (server != null) server.stop(); } catch (Throwable t) { log.warn("Error stopping Hot Rod server", t); } } } } /** * Invoke a task using a remote cache manager. This method guarantees that * the remote manager used in the task will be cleaned up after the task has * completed, regardless of the task outcome. * * @param c task to execute * @throws Exception if the task fails somehow */ public static void withRemoteCacheManager(RemoteCacheManagerCallable c) { try { c.call(); } finally { killRemoteCacheManager(c.rcm); } } public static <K, V> void withClientListener( RemoteCacheSupplier<K> l, Consumer<RemoteCache<K, V>> cons) { l.get().addClientListener(l); try { cons.accept(l.get()); } finally { l.get().removeClientListener(l); } } public static <K, V> void withClientListener(RemoteCacheSupplier<K> listener, Object[] fparams, Object[] cparams, Consumer<RemoteCache<K, V>> cons) { listener.get().addClientListener(listener, fparams, cparams); try { cons.accept(listener.get()); } finally { listener.get().removeClientListener(listener); } } public static <K> long entryVersion(Cache<byte[], ?> cache, K key) { byte[] lookupKey; try { lookupKey = toBytes(key); } catch (Exception e) { throw new AssertionError(e); } Metadata meta = cache.getAdvancedCache().getCacheEntry(lookupKey).getMetadata(); return ((NumericVersion) meta.version()).getVersion(); } public static byte[] toBytes(Object key) { try { return new GenericJBossMarshaller().objectToByteBuffer(key); } catch (Exception e) { throw new AssertionError(e); } } public static String getServersString(HotRodServer... servers) { StringBuilder builder = new StringBuilder(); for (HotRodServer server : servers) { builder.append("localhost").append(':').append(server.getPort()).append(";"); } return builder.toString(); } public static RemoteCacheManager getRemoteCacheManager(HotRodServer server) { ConfigurationBuilder builder = new ConfigurationBuilder(); builder.addServer() .host(server.getHost()) .port(server.getPort()); return new InternalRemoteCacheManager(builder.build()); } public static byte[] getKeyForServer(HotRodServer primaryOwner) { return getKeyForServer(primaryOwner, null); } public static byte[] getKeyForServer(HotRodServer primaryOwner, String cacheName) { GenericJBossMarshaller marshaller = new GenericJBossMarshaller(); Cache<?, ?> cache = cacheName != null ? primaryOwner.getCacheManager().getCache(cacheName) : primaryOwner.getCacheManager().getCache(); Random r = new Random(); byte[] dummy = new byte[8]; int attemptsLeft = 1000; try { do { r.nextBytes(dummy); attemptsLeft--; } while (!isFirstOwner(cache, marshaller.objectToByteBuffer(dummy)) && attemptsLeft >= 0); } catch (IOException e) { throw new AssertionError(e); } catch (InterruptedException e) { throw new AssertionError(e); } if (attemptsLeft < 0) throw new IllegalStateException("Could not find any key owned by " + primaryOwner); log.infof("Binary key %s hashes to [cluster=%s,hotrod=%s]", Util.printArray(dummy, false), primaryOwner.getCacheManager().getAddress(), primaryOwner.getAddress()); return dummy; } public static Integer getIntKeyForServer(HotRodServer primaryOwner) { return getIntKeyForServer(primaryOwner, null); } public static Integer getIntKeyForServer(HotRodServer primaryOwner, String cacheName) { Cache<?, ?> cache = cacheName != null ? primaryOwner.getCacheManager().getCache(cacheName) : primaryOwner.getCacheManager().getCache(); Random r = new Random(); byte[] dummy; Integer dummyInt; int attemptsLeft = 1000; do { dummyInt = r.nextInt(); dummy = toBytes(dummyInt); attemptsLeft--; } while (!isFirstOwner(cache, dummy) && attemptsLeft >= 0); if (attemptsLeft < 0) throw new IllegalStateException("Could not find any key owned by " + primaryOwner); log.infof("Integer key %s hashes to [cluster=%s,hotrod=%s]", dummyInt, primaryOwner.getCacheManager().getAddress(), primaryOwner.getAddress()); return dummyInt; } /** * Get a split-personality key, whose POJO version hashes to the primary * owner passed in, but it's binary version does not. */ public static Integer getSplitIntKeyForServer(HotRodServer primaryOwner, HotRodServer binaryOwner, String cacheName) { Cache<?, ?> cache = cacheName != null ? primaryOwner.getCacheManager().getCache(cacheName) : primaryOwner.getCacheManager().getCache(); Cache<?, ?> binaryOwnerCache = cacheName != null ? binaryOwner.getCacheManager().getCache(cacheName) : binaryOwner.getCacheManager().getCache(); Random r = new Random(); byte[] dummy; Integer dummyInt; int attemptsLeft = 1000; boolean primaryOwnerFound = false; boolean binaryOwnerFound = false; do { dummyInt = r.nextInt(); dummy = toBytes(dummyInt); attemptsLeft--; primaryOwnerFound = isFirstOwner(cache, dummyInt); binaryOwnerFound = isFirstOwner(binaryOwnerCache, dummy); } while (!(primaryOwnerFound && binaryOwnerFound) && attemptsLeft >= 0); if (attemptsLeft < 0) throw new IllegalStateException("Could not find any key owned by " + primaryOwner); log.infof("Integer key [pojo=%s,bytes=%s] hashes to [cluster=%s,hotrod=%s], but the binary version's owner is [cluster=%s,hotrod=%s]", Util.toHexString(dummy), dummyInt, primaryOwner.getCacheManager().getAddress(), primaryOwner.getAddress(), binaryOwner.getCacheManager().getAddress(), binaryOwner.getAddress()); return dummyInt; } public static <T extends FailoverRequestBalancingStrategy> T getLoadBalancer(RemoteCacheManager client) { TcpTransportFactory transportFactory = null; if (client instanceof InternalRemoteCacheManager) { transportFactory = (TcpTransportFactory) ((InternalRemoteCacheManager) client).getTransportFactory(); } else { transportFactory = TestingUtil.extractField(client, "transportFactory"); } return (T) transportFactory.getBalancer(HotRodConstants.DEFAULT_CACHE_NAME_BYTES); } public static void findServerAndKill(RemoteCacheManager client, Collection<HotRodServer> servers, Collection<EmbeddedCacheManager> cacheManagers) { InetSocketAddress addr = (InetSocketAddress) getLoadBalancer(client).nextServer(null); for (HotRodServer server : servers) { if (server.getPort() == addr.getPort()) { HotRodClientTestingUtil.killServers(server); TestingUtil.killCacheManagers(server.getCacheManager()); cacheManagers.remove(server.getCacheManager()); TestingUtil.blockUntilViewsReceived(50000, false, cacheManagers); } } } public static void withScript(EmbeddedCacheManager cm, String scriptPath, Consumer<String> f) { ScriptingManager scriptingManager = cm.getGlobalComponentRegistry().getComponent(ScriptingManager.class); String scriptName = scriptPath.replaceAll("\\/", ""); try { loadScript(scriptName, scriptingManager, scriptPath); f.accept(scriptName); } finally { scriptingManager.removeScript(scriptName); } } public static String loadScript(String scriptName, ScriptingManager scriptingManager, String fileName) { try (InputStream is = HotRodClientTestingUtil.class.getResourceAsStream(fileName)) { String script = TestingUtil.loadFileAsString(is); scriptingManager.addScript(scriptName, script); return scriptName; } catch (IOException e) { throw new AssertionError(e); } } public static void withScript(BasicCache<String, String> scriptCache, String scriptPath, Consumer<String> f) { String scriptName = scriptPath.replaceAll("\\/", ""); try { loadScript(scriptName, scriptCache, scriptPath); f.accept(scriptName); } finally { scriptCache.remove(scriptName); } } public static String loadScript(String scriptName, BasicCache<String, String> scriptCache, String fileName) { try (InputStream is = HotRodClientTestingUtil.class.getResourceAsStream(fileName)) { String script = TestingUtil.loadFileAsString(is); scriptCache.put(scriptName, script); return scriptName; } catch (IOException e) { throw new AssertionError(e); } } }