/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ignite.internal.processors.cache.query.continuous; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; import javax.cache.configuration.Factory; import javax.cache.configuration.MutableCacheEntryListenerConfiguration; import javax.cache.event.CacheEntryCreatedListener; import javax.cache.event.CacheEntryEvent; import javax.cache.event.CacheEntryEventFilter; import javax.cache.event.CacheEntryListener; import javax.cache.event.CacheEntryListenerException; import javax.cache.event.CacheEntryRemovedListener; import javax.cache.event.CacheEntryUpdatedListener; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteException; import org.apache.ignite.cache.query.ContinuousQuery; import org.apache.ignite.cache.query.QueryCursor; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.util.lang.GridAbsPredicate; import org.apache.ignite.internal.util.typedef.PA; import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.jsr166.ConcurrentHashMap8; import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC; import static org.apache.ignite.cache.CacheMode.PARTITIONED; import static org.apache.ignite.cache.CacheMode.REPLICATED; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; /** */ @SuppressWarnings("unchecked") public class GridCacheContinuousQueryMultiNodesFilteringTest extends GridCommonAbstractTest { /** */ private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true); /** */ private static final int SERVER_GRIDS_COUNT = 6; /** */ public static final int KEYS = 2_000; /** Cache entry operations' counts. */ private static final ConcurrentMap<String, AtomicInteger> opCounts = new ConcurrentHashMap8<>(); /** Client. */ private static boolean client = false; /** {@inheritDoc} */ @Override protected void afterTest() throws Exception { stopAllGrids(); client = false; super.afterTest(); } /** */ public void testFiltersAndListeners() throws Exception { for (int i = 1; i <= SERVER_GRIDS_COUNT; i++) startGrid(i, false); startGrid(SERVER_GRIDS_COUNT + 1, true); for (int i = 1; i <= SERVER_GRIDS_COUNT + 1; i++) { for (int j = 0; j < i; j++) { jcache(i, "part" + i).put("k" + j, "v0"); jcache(i, "repl" + i).put("k" + j, "v0"); // Should trigger updates jcache(i, "part" + i).put("k" + j, "v1"); jcache(i, "repl" + i).put("k" + j, "v1"); jcache(i, "part" + i).remove("k" + j); jcache(i, "repl" + i).remove("k" + j); } } for (int i = 1; i <= SERVER_GRIDS_COUNT + 1; i++) { // For each i, we did 3 ops on 2 caches on i keys, hence expected number. final int expTotal = i * 3 * 2; final int i0 = i; GridTestUtils.waitForCondition(new GridAbsPredicate() { @Override public boolean apply() { return opCounts.get("qry" + i0 + "_total").get() == expTotal; } }, 5000); int partInserts = opCounts.get("part" + i + "_ins").get(); int replInserts = opCounts.get("repl" + i + "_ins").get(); int partUpdates = opCounts.get("part" + i + "_upd").get(); int replUpdates = opCounts.get("repl" + i + "_upd").get(); int partRemoves = opCounts.get("part" + i + "_rmv").get(); int replRemoves = opCounts.get("repl" + i + "_rmv").get(); int totalQryOps = opCounts.get("qry" + i + "_total").get(); assertEquals(i, partInserts); assertEquals(i, replInserts); assertEquals(i, partUpdates); assertEquals(i, replUpdates); assertEquals(i, partRemoves); assertEquals(i, replRemoves); assertEquals(expTotal, totalQryOps); assertEquals(totalQryOps, partInserts + replInserts + partUpdates + replUpdates + partRemoves + replRemoves); } } /** * @throws Exception If failed. */ public void testWithNodeFilter() throws Exception { List<QueryCursor> qryCursors = new ArrayList<>(); final int nodesCnt = 3; startGridsMultiThreaded(nodesCnt); awaitPartitionMapExchange(); CacheConfiguration ccfg = cacheConfiguration(new NodeFilterByRegexp(".*(0|1)$")); grid(0).createCache(ccfg); final AtomicInteger cntr = new AtomicInteger(); final ConcurrentMap<ClusterNode, Set<Integer>> maps = new ConcurrentHashMap<>(); final AtomicBoolean doubleNtfFail = new AtomicBoolean(false); CacheEntryUpdatedListener<Integer, Integer> lsnr = new CacheEntryUpdatedListener<Integer, Integer>() { @Override public void onUpdated(Iterable<CacheEntryEvent<? extends Integer, ? extends Integer>> evts) throws CacheEntryListenerException { for (CacheEntryEvent<? extends Integer, ? extends Integer> e : evts) { cntr.incrementAndGet(); ClusterNode node = ((Ignite)e.getSource().unwrap(Ignite.class)).cluster().localNode(); Set<Integer> set = maps.get(node); if (set == null) { set = new ConcurrentSkipListSet<>(); Set<Integer> oldVal = maps.putIfAbsent(node, set); set = oldVal != null ? oldVal : set; } if (!set.add(e.getValue())) doubleNtfFail.set(false); } } }; for (int i = 0; i < nodesCnt; i++) { ContinuousQuery<Integer, Integer> qry = new ContinuousQuery<>(); qry.setLocalListener(lsnr); Ignite ignite = grid(i); log.info("Try to start CQ on node: " + ignite.cluster().localNode().id()); qryCursors.add(ignite.cache(ccfg.getName()).query(qry)); log.info("CQ started on node: " + ignite.cluster().localNode().id()); } client = true; startGrid(nodesCnt); awaitPartitionMapExchange(); ContinuousQuery<Integer, Integer> qry = new ContinuousQuery<>(); qry.setLocalListener(lsnr); qryCursors.add(grid(nodesCnt).cache(ccfg.getName()).query(qry)); for (int i = 0; i <= nodesCnt; i++) { for (int key = 0; key < KEYS; key++) { int val = (i * KEYS) + key; grid(i).cache(ccfg.getName()).put(val, val); } } assertTrue(GridTestUtils.waitForCondition(new PA() { @Override public boolean apply() { return cntr.get() >= 2 * (nodesCnt + 1) * KEYS; } }, 5000L)); assertFalse("Got duplicate", doubleNtfFail.get()); for (int i = 0; i < (nodesCnt + 1) * KEYS; i++) { for (Map.Entry<ClusterNode, Set<Integer>> e : maps.entrySet()) assertTrue("Lost event on node: " + e.getKey().id() + ", event: " + i, e.getValue().remove(i)); } for (Map.Entry<ClusterNode, Set<Integer>> e : maps.entrySet()) assertTrue("Unexpected event on node: " + e.getKey(), e.getValue().isEmpty()); assertEquals("Not expected count of CQ", nodesCnt + 1, qryCursors.size()); for (QueryCursor cur : qryCursors) cur.close(); } /** */ private Ignite startGrid(final int idx, boolean isClientMode) throws Exception { String igniteInstanceName = getTestIgniteInstanceName(idx); IgniteConfiguration cfg = optimize(getConfiguration(igniteInstanceName)).setClientMode(isClientMode); ((TcpDiscoverySpi)cfg.getDiscoverySpi()).setIpFinder(IP_FINDER); cfg.setUserAttributes(Collections.singletonMap("idx", idx)); Ignite node = startGrid(igniteInstanceName, cfg); IgnitePredicate<ClusterNode> nodeFilter = new NodeFilter(idx); String partCacheName = "part" + idx; IgniteCache partCache = node.createCache(defaultCacheConfiguration().setName("part" + idx) .setCacheMode(PARTITIONED).setBackups(1).setNodeFilter(nodeFilter)); opCounts.put(partCacheName + "_ins", new AtomicInteger()); opCounts.put(partCacheName + "_upd", new AtomicInteger()); opCounts.put(partCacheName + "_rmv", new AtomicInteger()); partCache.registerCacheEntryListener(new ListenerConfiguration(partCacheName, ListenerConfiguration.Op.INSERT)); partCache.registerCacheEntryListener(new ListenerConfiguration(partCacheName, ListenerConfiguration.Op.UPDATE)); partCache.registerCacheEntryListener(new ListenerConfiguration(partCacheName, ListenerConfiguration.Op.REMOVE)); String replCacheName = "repl" + idx; IgniteCache replCache = node.createCache(defaultCacheConfiguration().setName("repl" + idx) .setCacheMode(REPLICATED).setNodeFilter(nodeFilter)); opCounts.put(replCacheName + "_ins", new AtomicInteger()); opCounts.put(replCacheName + "_upd", new AtomicInteger()); opCounts.put(replCacheName + "_rmv", new AtomicInteger()); replCache.registerCacheEntryListener(new ListenerConfiguration(replCacheName, ListenerConfiguration.Op.INSERT)); replCache.registerCacheEntryListener(new ListenerConfiguration(replCacheName, ListenerConfiguration.Op.UPDATE)); replCache.registerCacheEntryListener(new ListenerConfiguration(replCacheName, ListenerConfiguration.Op.REMOVE)); opCounts.put("qry" + idx + "_total", new AtomicInteger()); ContinuousQuery qry = new ContinuousQuery(); qry.setRemoteFilterFactory(new EntryEventFilterFactory(idx)); qry.setLocalListener(new CacheEntryUpdatedListener() { /** {@inheritDoc} */ @Override public void onUpdated(Iterable evts) { opCounts.get("qry" + idx + "_total").incrementAndGet(); } }); partCache.query(qry); replCache.query(qry); return node; } /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); cfg.setClientMode(client); return cfg; } /** * @param filter Node filter. * @return Cache configuration. */ private CacheConfiguration cacheConfiguration(NodeFilterByRegexp filter) { return new CacheConfiguration("test-cache-cq") .setBackups(1) .setNodeFilter(filter) .setAtomicityMode(ATOMIC) .setWriteSynchronizationMode(FULL_SYNC) .setCacheMode(PARTITIONED); } /** */ private final static class ListenerConfiguration extends MutableCacheEntryListenerConfiguration { /** Operation. */ enum Op { /** Insert. */ INSERT, /** Update. */ UPDATE, /** Remove. */ REMOVE } /** */ ListenerConfiguration(final String cacheName, final Op op) { super(new Factory<CacheEntryListener>() { /** {@inheritDoc} */ @Override public CacheEntryListener create() { switch (op) { case INSERT: return new CacheEntryCreatedListener() { /** {@inheritDoc} */ @Override public void onCreated(Iterable iterable) { for (Object evt : iterable) opCounts.get(cacheName + "_ins").getAndIncrement(); } }; case UPDATE: return new CacheEntryUpdatedListener() { /** {@inheritDoc} */ @Override public void onUpdated(Iterable iterable) { for (Object evt : iterable) opCounts.get(cacheName + "_upd").getAndIncrement(); } }; case REMOVE: return new CacheEntryRemovedListener() { /** {@inheritDoc} */ @Override public void onRemoved(Iterable iterable) { for (Object evt : iterable) opCounts.get(cacheName + "_rmv").getAndIncrement(); } }; default: throw new IgniteException(new IllegalArgumentException()); } } }, null, true, false); } } /** */ private final static class EntryEventFilterFactory implements Factory<CacheEntryEventFilter> { /** */ @IgniteInstanceResource private Ignite ignite; /** Grid index to determine whether node filter has been invoked. */ private final int idx; /** */ private EntryEventFilterFactory(int idx) { this.idx = idx; } /** {@inheritDoc} */ @Override public CacheEntryEventFilter create() { return new CacheEntryEventFilter() { /** {@inheritDoc} */ @Override public boolean evaluate(CacheEntryEvent evt) throws CacheEntryListenerException { int evtNodeIdx = (Integer)(ignite.cluster().localNode().attributes().get("idx")); assertTrue(evtNodeIdx % 2 == idx % 2); return true; } }; } } /** */ private final static class NodeFilter implements IgnitePredicate<ClusterNode> { /** */ private final int idx; /** */ private NodeFilter(int idx) { this.idx = idx; } /** {@inheritDoc} */ @Override public boolean apply(ClusterNode clusterNode) { return ((Integer)clusterNode.attributes().get("idx") % 2) == idx % 2; } } /** */ private final static class NodeFilterByRegexp implements IgnitePredicate<ClusterNode> { /** */ private final Pattern pattern; /** */ private NodeFilterByRegexp(String regExp) { this.pattern = Pattern.compile(regExp); } /** {@inheritDoc} */ @Override public boolean apply(ClusterNode clusterNode) { return pattern.matcher(clusterNode.id().toString()).matches(); } } }