package org.infinispan.server.hotrod.event; import static org.infinispan.server.hotrod.OperationStatus.Success; import static org.infinispan.server.hotrod.test.HotRodTestingUtil.assertStatus; import static org.infinispan.server.hotrod.test.HotRodTestingUtil.hotRodCacheConfiguration; import static org.infinispan.server.hotrod.test.HotRodTestingUtil.k; import static org.infinispan.server.hotrod.test.HotRodTestingUtil.v; import static org.infinispan.server.hotrod.test.HotRodTestingUtil.withClientListener; import java.io.Serializable; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import org.infinispan.Cache; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.marshall.core.ExternalPojo; import org.infinispan.notifications.cachelistener.event.Event; import org.infinispan.notifications.cachelistener.filter.CacheEventConverter; import org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory; import org.infinispan.notifications.cachelistener.filter.CacheEventFilter; import org.infinispan.notifications.cachelistener.filter.CacheEventFilterFactory; import org.infinispan.server.hotrod.HotRodMultiNodeTest; import org.infinispan.server.hotrod.HotRodServer; import org.infinispan.server.hotrod.test.HotRodClient; import org.infinispan.server.hotrod.test.HotRodTestingUtil; import org.infinispan.server.hotrod.test.TestClientListener; import org.infinispan.server.hotrod.test.TestResponse; import org.infinispan.test.TestingUtil; import org.infinispan.util.KeyValuePair; import org.testng.annotations.Test; /** * @author Galder ZamarreƱo */ @Test(groups = "functional") public abstract class AbstractHotRodClusterEventsTest extends HotRodMultiNodeTest { private ArrayList<AcceptedKeyFilterFactory> filters = new ArrayList<>(); private ArrayList<AcceptedKeyValueConverterFactory> converters = new ArrayList<>(); @Override protected String cacheName() { return "remote-clustered-events"; } @Override protected int nodeCount() { return 3; } @Override protected ConfigurationBuilder createCacheConfig() { return hotRodCacheConfiguration(getDefaultClusteredCacheConfig(cacheMode, false)); } @Override protected HotRodServer startTestHotRodServer(EmbeddedCacheManager cacheManager, int port) { HotRodServer server = HotRodTestingUtil.startHotRodServer(cacheManager, port); filters.add(new AcceptedKeyFilterFactory()); server.addCacheEventFilterFactory("accepted-key-filter-factory", filters.get(0)); converters.add(new AcceptedKeyValueConverterFactory()); server.addCacheEventConverterFactory("accepted-keyvalue-converter-factory", converters.get(0)); return server; } public void testEventForwarding(Method m) { // Registering listener in one node and executing operations against // different nodes should still result in events received HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); HotRodClient client3 = clients().get(2); EventLogListener listener1 = new EventLogListener(); withClientListener(client1, listener1, Optional.empty(), Optional.empty(), false, true, () -> { byte[] key = k(m); client2.put(key, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), key); client3.put(key, 0, 0, v(m, "v2-")); listener1.expectOnlyModifiedEvent(anyCache(), key); client2.remove(key); listener1.expectOnlyRemovedEvent(anyCache(), key); }); } public void testNoEventsAfterRemovingListener(Method m) { HotRodClient client1 = clients().get(0); EventLogListener listener1 = new EventLogListener(); byte[] key = k(m); withClientListener(client1, listener1, Optional.empty(), Optional.empty(), false, true, () -> { client1.put(key, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), key); client1.put(key, 0, 0, v(m, "v2-")); listener1.expectOnlyModifiedEvent(anyCache(), key); client1.remove(key); listener1.expectOnlyRemovedEvent(anyCache(), key); }); client1.put(key, 0, 0, v(m)); listener1.expectNoEvents(Optional.empty()); client1.remove(key); listener1.expectNoEvents(Optional.empty()); } public void testNoEventsAfterRemovingListenerInDifferentNode(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); EventLogListener listener1 = new EventLogListener(); byte[] key = k(m); assertStatus(client1.addClientListener(listener1, false, Optional.empty(), Optional.empty(), true), Success); try { client1.put(key, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), key); client1.put(key, 0, 0, v(m, "v2-")); listener1.expectOnlyModifiedEvent(anyCache(), key); client1.remove(key); listener1.expectOnlyRemovedEvent(anyCache(), key); // Use a client connected to a different node to attempt trying to remove listener client2.removeClientListener(listener1.getId()); // The remoint has no effect since the listener information is not clustered // Remoint needs to be done in the node where the listener was added client1.put(key, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), key); client1.remove(key); listener1.expectOnlyRemovedEvent(anyCache(), key); } finally { assertStatus(client1.removeClientListener(listener1.getId()), Success); } } public void testClientDisconnectListenerCleanup(Method m) throws InterruptedException { HotRodClient client1 = clients().get(0); HotRodClient newClient = new HotRodClient("127.0.0.1", servers().get(1).getPort(), cacheName(), 60, protocolVersion()); EventLogListener listener = new EventLogListener(); assertStatus(newClient.addClientListener(listener, false, Optional.empty(), Optional.empty(), true), Success); byte[] key = k(m); client1.put(key, 0, 0, v(m)); listener.expectOnlyCreatedEvent(anyCache(), key); newClient.stop().await(); client1.put(k(m, "k2-"), 0, 0, v(m)); listener.expectNoEvents(Optional.empty()); client1.remove(key); client1.remove(k(m, "k2-")); } public void testFailoverSendsEventsForNewContent(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); HotRodClient client3 = clients().get(2); EventLogListener listener1 = new EventLogListener(); EventLogListener listener2 = new EventLogListener(); withClientListener(client1, listener1, Optional.empty(), Optional.empty(), false, true, () -> { byte[] key = k(m); client2.put(key, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), key); client2.remove(key); listener1.expectOnlyRemovedEvent(anyCache(), key); HotRodServer newServer = startClusteredServer(servers().get(2).getPort() + 50); try { HotRodClient client4 = new HotRodClient("127.0.0.1", newServer.getPort(), cacheName(), 60, protocolVersion()); withClientListener(client4, listener2, Optional.empty(), Optional.empty(), false, true, () -> { byte[] newKey = k(m, "k2-"); client3.put(newKey, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), newKey); listener2.expectOnlyCreatedEvent(anyCache(), newKey); client1.put(newKey, 0, 0, v(m, "v2-")); listener1.expectOnlyModifiedEvent(anyCache(), newKey); listener2.expectOnlyModifiedEvent(anyCache(), newKey); client4.remove(newKey); listener1.expectOnlyRemovedEvent(anyCache(), newKey); listener2.expectOnlyRemovedEvent(anyCache(), newKey); }); } finally { stopClusteredServer(newServer); TestingUtil.waitForNoRebalance( cache(0, cacheName()), cache(1, cacheName()), cache(2, cacheName())); } client3.put(key, 0, 0, v(m, "v2-")); listener1.expectOnlyCreatedEvent(anyCache(), key); listener2.expectNoEvents(Optional.empty()); client3.put(key, 0, 0, v(m, "v3-")); listener1.expectOnlyModifiedEvent(anyCache(), key); listener2.expectNoEvents(Optional.empty()); client2.remove(key); listener1.expectOnlyRemovedEvent(anyCache(), key); listener2.expectNoEvents(Optional.empty()); }); } public void testFilteringInCluster(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); EventLogListener listener1 = new EventLogListener(); Optional<KeyValuePair<String, List<byte[]>>> filterFactory = Optional.of(new KeyValuePair<String, List<byte[]>>("accepted-key-filter-factory", Collections.emptyList())); byte[] key1 = k(m, "k1-"); withClusterClientListener(client1, listener1, filterFactory, Optional.empty(), Optional.of(key1), false, () -> { client2.put(k(m, "k-99"), 0, 0, v(m)); listener1.expectNoEvents(Optional.empty()); client2.remove(k(m, "k-99")); listener1.expectNoEvents(Optional.empty()); client2.put(key1, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), key1); client1.remove(key1); listener1.expectOnlyRemovedEvent(anyCache(), key1); }); } public void testParameterBasedFilteringInCluster(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); EventLogListener listener1 = new EventLogListener(); byte[] dynamicAcceptedKey = new byte[]{4, 5, 6}; Optional<KeyValuePair<String, List<byte[]>>> filterFactory = Optional.of( new KeyValuePair<>("accepted-key-filter-factory", Collections.singletonList(dynamicAcceptedKey))); withClusterClientListener(client1, listener1, filterFactory, Optional.empty(), Optional.empty(), false, () -> { byte[] key1 = k(m, "k1-"); client2.put(k(m, "k-99"), 0, 0, v(m)); listener1.expectNoEvents(Optional.empty()); client2.remove(k(m, "k-99")); listener1.expectNoEvents(Optional.empty()); client2.put(key1, 0, 0, v(m)); listener1.expectNoEvents(Optional.empty()); client2.put(dynamicAcceptedKey, 0, 0, v(m)); listener1.expectOnlyCreatedEvent(anyCache(), dynamicAcceptedKey); client1.remove(dynamicAcceptedKey); listener1.expectOnlyRemovedEvent(anyCache(), dynamicAcceptedKey); }); } public void testConversionInCluster(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); EventLogListener listener1 = new EventLogListener(); Optional<KeyValuePair<String, List<byte[]>>> converterFactory = Optional .of(new KeyValuePair<String, List<byte[]>>("accepted-keyvalue-converter-factory", Collections.emptyList())); final byte[] key1 = k(m, "k1-"); withClusterClientListener(client1, listener1, Optional.empty(), converterFactory, Optional.of(key1), false, () -> { byte[] value = v(m); byte[] key99 = k(m, "k-99"); client2.put(key99, 0, 0, v(m)); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(key99)); client2.put(key1, 0, 0, v(m)); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(key1, value)); client2.remove(key99); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(key99)); client2.remove(key1); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(key1)); }); } public void testParameterBasedConversionInCluster(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); EventLogListener listener1 = new EventLogListener(); byte[] convertedKey = new byte[]{4, 5, 6}; Optional<KeyValuePair<String, List<byte[]>>> converteFactory = Optional.of( new KeyValuePair<>("accepted-keyvalue-converter-factory", Collections.singletonList(new byte[]{4, 5, 6}))); withClusterClientListener(client1, listener1, Optional.empty(), converteFactory, Optional.empty(), false, () -> { byte[] key1 = k(m, "k1-"); byte[] value = v(m); byte[] key99 = k(m, "k-99"); client2.put(key99, 0, 0, v(m)); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(key99)); client2.put(key1, 0, 0, v(m)); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(key1)); client2.put(convertedKey, 0, 0, v(m)); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(convertedKey, value)); client1.remove(convertedKey); listener1.expectSingleCustomEvent(anyCache(), addLengthPrefix(convertedKey)); }); } public void testEventReplayAfterAddingListenerInCluster(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); HotRodClient client3 = clients().get(2); byte[] k1 = k(m, "k1-"); byte[] v1 = v(m, "v1-"); byte[] k2 = k(m, "k2-"); byte[] v2 = v(m, "v2-"); byte[] k3 = k(m, "k3-"); byte[] v3 = v(m, "v3-"); client1.put(k1, 0, 0, v1); client2.put(k2, 0, 0, v2); client3.put(k3, 0, 0, v3); EventLogListener listener1 = new EventLogListener(); withClientListener(client1, listener1, Optional.empty(), Optional.empty(), true, true, () -> { List<byte[]> keys = Arrays.asList(k1, k2, k3); listener1.expectUnorderedEvents(anyCache(), keys, Event.Type.CACHE_ENTRY_CREATED); client1.remove(k1); listener1.expectOnlyRemovedEvent(anyCache(), k1); client2.remove(k2); listener1.expectOnlyRemovedEvent(anyCache(), k2); client3.remove(k3); listener1.expectOnlyRemovedEvent(anyCache(), k3); }); } public void testNoEventReplayAfterAddingListenerInCluster(Method m) { HotRodClient client1 = clients().get(0); HotRodClient client2 = clients().get(1); HotRodClient client3 = clients().get(2); byte[] k1 = k(m, "k1-"); byte[] v1 = v(m, "v1-"); byte[] k2 = k(m, "k2-"); byte[] v2 = v(m, "v2-"); byte[] k3 = k(m, "k3-"); byte[] v3 = v(m, "v3-"); client1.put(k1, 0, 0, v1); client2.put(k2, 0, 0, v2); client3.put(k3, 0, 0, v3); EventLogListener listener1 = new EventLogListener(); withClientListener(client1, listener1, Optional.empty(), Optional.empty(), false, true, () -> { listener1.expectNoEvents(Optional.empty()); client1.remove(k1); listener1.expectOnlyRemovedEvent(anyCache(), k1); client2.remove(k2); listener1.expectOnlyRemovedEvent(anyCache(), k2); client3.remove(k3); listener1.expectOnlyRemovedEvent(anyCache(), k3); }); } private Cache<byte[], byte[]> anyCache() { return cacheManagers.get(0).<byte[], byte[]>getCache(cacheName()).getAdvancedCache(); } private void withClusterClientListener(HotRodClient client, TestClientListener listener, Optional<KeyValuePair<String, List<byte[]>>> filterFactory, Optional<KeyValuePair<String, List<byte[]>>> converterFactory, Optional<byte[]> staticKey, boolean includeState, Runnable fn) { filters.forEach(factory -> factory.staticKey = staticKey); converters.forEach(factory -> factory.staticKey = staticKey); TestResponse response = client.addClientListener(listener, includeState, filterFactory, converterFactory, true); assertStatus(response, Success); try { fn.run(); } finally { assertStatus(client.removeClientListener(listener.getId()), Success); filters.forEach(factory -> factory.staticKey = Optional.empty()); converters.forEach(factory -> factory.staticKey = Optional.empty()); } } public static byte[] addLengthPrefix(byte[] key) { byte keyLength = (byte) key.length; ByteBuffer buffer = ByteBuffer.allocate(keyLength + 1); buffer.put(keyLength); buffer.put(key); return buffer.array(); } public static byte[] addLengthPrefix(byte[] key, byte[] value) { byte keyLength = (byte) key.length; byte valueLength = (byte) value.length; ByteBuffer buffer = ByteBuffer.allocate(keyLength + valueLength + 2); buffer.put(keyLength); buffer.put(key); buffer.put(valueLength); buffer.put(value); return buffer.array(); } private static class AcceptedKeyFilterFactory implements CacheEventFilterFactory, Serializable, ExternalPojo { Optional<byte[]> staticKey = null; @Override public <K, V> CacheEventFilter<K, V> getFilter(Object[] params) { return (CacheEventFilter<K, V> & Serializable & ExternalPojo) ((key, oldValue, oldMetadata, newValue, newMetadata, eventType) -> { byte[] checkKey = staticKey.orElseGet(() -> (byte[]) params[0]); return Arrays.equals(checkKey, (byte[]) key); }); } } private static class AcceptedKeyValueConverterFactory implements CacheEventConverterFactory, Serializable, ExternalPojo { Optional<byte[]> staticKey = null; @Override public <K, V, C> CacheEventConverter<K, V, C> getConverter(Object[] params) { return (CacheEventConverter<K, V, C>) (CacheEventConverter<byte[], byte[], byte[]> & Serializable & ExternalPojo) ((key, oldValue, oldMetadata, newValue, newMetadata, eventType) -> { byte[] checkKey = staticKey.orElseGet(() -> (byte[]) params[0]); if (newValue == null || !Arrays.equals(checkKey, key)) { return addLengthPrefix(key); } else { return addLengthPrefix(key, newValue); } }); } } }