/* * 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.distributed.near; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import javax.cache.expiry.Duration; import javax.cache.expiry.ExpiryPolicy; import javax.cache.expiry.TouchedExpiryPolicy; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteTransactions; import org.apache.ignite.cache.CachePeekMode; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.configuration.NearCacheConfiguration; import org.apache.ignite.events.Event; import org.apache.ignite.internal.processors.cache.GridCacheEntryEx; import org.apache.ignite.internal.processors.cache.GridCacheEntryRemovedException; import org.apache.ignite.internal.util.lang.GridAbsPredicateX; import org.apache.ignite.internal.util.lang.IgnitePair; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.P1; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.transactions.Transaction; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_LOCKED; import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_UNLOCKED; /** * */ public class GridCacheNearOnlyMultiNodeFullApiSelfTest extends GridCachePartitionedMultiNodeFullApiSelfTest { /** */ private static AtomicInteger cnt; /** Index of the near-only instance. */ protected Integer nearIdx; /** {@inheritDoc} */ @Override protected void beforeTestsStarted() throws Exception { cnt = new AtomicInteger(); super.beforeTestsStarted(); for (int i = 0; i < gridCount(); i++) { if (ignite(i).configuration().isClientMode()) { if (clientHasNearCache()) ignite(i).createNearCache(DEFAULT_CACHE_NAME, new NearCacheConfiguration<>()); else ignite(i).cache(DEFAULT_CACHE_NAME); break; } } } /** * @return If client node has near cache. */ protected boolean clientHasNearCache() { return true; } /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); if (cnt.getAndIncrement() == 0 || (cnt.get() > gridCount() && cnt.get() % gridCount() == 0)) { info("Use grid '" + igniteInstanceName + "' as near-only."); cfg.setClientMode(true); cfg.setCacheConfiguration(); } return cfg; } /** {@inheritDoc} */ @Override protected CacheConfiguration cacheConfiguration(String igniteInstanceName) throws Exception { CacheConfiguration cfg = super.cacheConfiguration(igniteInstanceName); cfg.setWriteSynchronizationMode(FULL_SYNC); return cfg; } /** {@inheritDoc} */ @Override protected void beforeTest() throws Exception { for (int i = 0; i < gridCount(); i++) { if (ignite(i).configuration().isClientMode()) { nearIdx = i; break; } } assert nearIdx != null : "Didn't find a near-only node."; dfltIgnite = grid(nearIdx); info("Near-only node: " + dfltIgnite.cluster().localNode().id()); super.beforeTest(); // Doing initial asserts. } /** {@inheritDoc} */ @Override public Collection<ClusterNode> affinityNodes() { info("Near node ID: " + grid(nearIdx).localNode().id()); return F.view(super.affinityNodes(), new P1<ClusterNode>() { @Override public boolean apply(ClusterNode n) { return !F.eq(grid(n).name(), grid(nearIdx).name()); } }); } /** * @return A not near-only cache. */ @Override protected IgniteCache<String, Integer> fullCache() { return nearIdx == 0 ? jcache(1) : jcache(0); } /** * @return For the purpose of this test returns the near-only instance. */ @Override protected IgniteCache<String, Integer> jcache() { return jcache(nearIdx); } /** {@inheritDoc} */ @Override protected IgniteTransactions transactions() { return grid(nearIdx).transactions(); } /** {@inheritDoc} */ @Override protected List<String> primaryKeysForCache(IgniteCache<String, Integer> cache, int cnt) throws IgniteCheckedException { if (cache.equals(jcache())) return super.primaryKeysForCache(fullCache(), cnt); return super.primaryKeysForCache(cache, cnt); } /** {@inheritDoc} */ @Override public void testSize() throws Exception { IgniteCache<String, Integer> nearCache = jcache(); int size = 10; Map<String, Integer> map = U.newLinkedHashMap(size); for (int i = 0; i < size; i++) map.put("key" + i, i); nearCache.putAll(map); affinityNodes(); // Just to ack cache configuration to log.. checkKeySize(map.keySet()); checkSize(map.keySet()); assertEquals(10, nearCache.localSize(CachePeekMode.ALL)); int fullCacheSize = 0; for (int i = 0; i < gridCount(); i++) fullCacheSize += jcache(i).localSize(); assertEquals("Invalid cache size", fullCacheSize, nearCache.size()); } /** {@inheritDoc} */ @Override public void testLoadAll() throws Exception { // Not needed for near-only cache. } /** * TODO GG-11133. * @throws Exception If failed. */ public void _testReaderTtlTx() throws Exception { // IgniteProcessProxy#transactions is not implemented. if (isMultiJvm()) return; checkReaderTtl(true); } /** * TODO GG-11133. * @throws Exception If failed. */ public void _testReaderTtlNoTx() throws Exception { checkReaderTtl(false); } /** * @param cache Cache. * @param key Entry key. * @return Pair [ttl, expireTime] for near cache entry; both values null if entry not found */ protected IgnitePair<Long> nearEntryTtl(IgniteCache cache, String key) { return executeOnLocalOrRemoteJvm(cache, new EntryTtlTask(key, false)); } /** * @param inTx If {@code true} starts explicit transaction. * @throws Exception If failed. */ private void checkReaderTtl(boolean inTx) throws Exception { int ttl = 1000; final ExpiryPolicy expiry = new TouchedExpiryPolicy(new Duration(TimeUnit.MILLISECONDS, ttl)); final IgniteCache<String, Integer> c = jcache(); final String key = primaryKeysForCache(fullCache(), 1).get(0); c.put(key, 1); info("Finished first put."); { IgnitePair<Long> entryTtl = entryTtl(fullCache(), key); assertEquals((Integer)1, c.get(key)); assertNotNull(entryTtl.get1()); assertNotNull(entryTtl.get2()); assertEquals(0, (long)entryTtl.get1()); assertEquals(0, (long)entryTtl.get2()); } long startTime = System.currentTimeMillis(); int fullIdx = nearIdx == 0 ? 1 : 0; // Now commit transaction and check that ttl and expire time have been saved. Transaction tx = inTx ? grid(fullIdx).transactions().txStart() : null; try { jcache(fullIdx).withExpiryPolicy(expiry).put(key, 1); if (tx != null) tx.commit(); } finally { if (tx != null) tx.close(); } long[] expireTimes = new long[gridCount()]; for (int i = 0; i < gridCount(); i++) { info("Checking grid: " + grid(i).localNode().id()); IgnitePair<Long> entryTtl = null; if (grid(i).affinity(DEFAULT_CACHE_NAME).isPrimaryOrBackup(grid(i).localNode(), key)) entryTtl = entryTtl(jcache(i), key); else if (i == nearIdx) entryTtl = nearEntryTtl(jcache(i), key); if (entryTtl != null) { assertNotNull(entryTtl.get1()); assertNotNull(entryTtl.get2()); assertEquals(ttl, (long)entryTtl.get1()); assertTrue(entryTtl.get2() > startTime); expireTimes[i] = entryTtl.get2(); } } // One more update from the same cache entry to ensure that expire time is shifted forward. U.sleep(100); tx = inTx ? grid(fullIdx).transactions().txStart() : null; try { jcache(fullIdx).withExpiryPolicy(expiry).put(key, 2); if (tx != null) tx.commit(); } finally { if (tx != null) tx.close(); } for (int i = 0; i < gridCount(); i++) { IgnitePair<Long> entryTtl = null; if (grid(i).affinity(DEFAULT_CACHE_NAME).isPrimaryOrBackup(grid(i).localNode(), key)) entryTtl = entryTtl(jcache(i), key); else if (i == nearIdx) entryTtl = nearEntryTtl(jcache(i), key); if (entryTtl != null) { assertNotNull(entryTtl.get1()); assertNotNull(entryTtl.get2()); assertEquals(ttl, (long)entryTtl.get1()); assertTrue(entryTtl.get2() > startTime); expireTimes[i] = entryTtl.get2(); } } // And one more update to ensure that ttl is not changed and expire time is not shifted forward. U.sleep(100); tx = inTx ? grid(fullIdx).transactions().txStart() : null; try { jcache(fullIdx).put(key, 4); } finally { if (tx != null) tx.commit(); } for (int i = 0; i < gridCount(); i++) { IgnitePair<Long> entryTtl = null; if (grid(i).affinity(DEFAULT_CACHE_NAME).isPrimaryOrBackup(grid(i).localNode(), key)) entryTtl = entryTtl(jcache(i), key); else if (i == nearIdx) entryTtl = nearEntryTtl(jcache(i), key); if (entryTtl != null) { assertNotNull(entryTtl.get1()); assertNotNull(entryTtl.get2()); assertEquals(ttl, (long)entryTtl.get1()); assertEquals(expireTimes[i], (long)entryTtl.get2()); } } // Avoid reloading from store. storeStgy.removeFromStore(key); assertTrue(GridTestUtils.waitForCondition(new GridAbsPredicateX() { @SuppressWarnings("unchecked") @Override public boolean applyx() throws IgniteCheckedException { try { Integer val = c.get(key); if (val != null) { info("Value is in cache [key=" + key + ", val=" + val + ']'); return false; } if (!internalCache(c).context().deferredDelete()) { GridCacheEntryEx e0 = internalCache(c).peekEx(key); return e0 == null || (e0.rawGet() == null && e0.valueBytes() == null); } else return true; } catch (GridCacheEntryRemovedException ignored) { // If e0.valueBytes() thrown this exception then entry has been removed. return true; } } }, Math.min(ttl * 10, getTestTimeout()))); // Ensure that old TTL and expire time are not longer "visible". { IgnitePair<Long> entryTtl = entryTtl(fullCache(), key); assertNotNull(entryTtl.get1()); assertNotNull(entryTtl.get2()); assertEquals(0, (long)entryTtl.get1()); assertEquals(0, (long)entryTtl.get2()); } // Ensure that next update will not pick old expire time. tx = inTx ? transactions().txStart() : null; try { c.put(key, 10); if (tx != null) tx.commit(); } finally { if (tx != null) tx.close(); } U.sleep(2000); { IgnitePair<Long> entryTtl = entryTtl(fullCache(), key); assertNotNull(entryTtl.get1()); assertNotNull(entryTtl.get2()); assertEquals(0, (long)entryTtl.get1()); assertEquals(0, (long)entryTtl.get2()); } } /** {@inheritDoc} */ @Override public void testClear() throws Exception { IgniteCache<String, Integer> nearCache = jcache(); IgniteCache<String, Integer> primary = fullCache(); Collection<String> keys = primaryKeysForCache(primary, 3); info("Keys: " + keys); Map<String, Integer> vals = new HashMap<>(); int i = 0; for (String key : keys) { nearCache.put(key, i); vals.put(key, i); i++; } for (String key : keys) assertEquals(vals.get(key), nearCache.localPeek(key, CachePeekMode.ONHEAP)); nearCache.clear(); for (String key : keys) assertNull(nearCache.localPeek(key, CachePeekMode.ONHEAP)); for (Map.Entry<String, Integer> entry : vals.entrySet()) nearCache.put(entry.getKey(), entry.getValue()); for (String key : keys) assertEquals(vals.get(key), nearCache.localPeek(key, CachePeekMode.ONHEAP)); String first = F.first(keys); Lock lock = nearCache.lock(first); lock.lock(); try { nearCache.clear(); assertEquals(vals.get(first), nearCache.localPeek(first, CachePeekMode.ONHEAP)); assertEquals(vals.get(first), primary.localPeek(first, CachePeekMode.ONHEAP)); } finally { lock.unlock(); } } /** {@inheritDoc} */ @Override public void testLocalClearKeys() throws Exception { IgniteCache<String, Integer> nearCache = jcache(); IgniteCache<String, Integer> primary = fullCache(); Collection<String> keys = primaryKeysForCache(primary, 3); int i = 0; for (String key : keys) nearCache.put(key, i++); String lastKey = F.last(keys); Set<String> keysToRmv = new HashSet<>(keys); keysToRmv.remove(lastKey); assert keysToRmv.size() > 1; nearCache.localClearAll(keysToRmv); for (String key : keys) { boolean found = nearCache.localPeek(key, CachePeekMode.ONHEAP) != null; if (keysToRmv.contains(key)) assertFalse("Found removed key " + key, found); else assertTrue("Not found key " + key, found); } } /** * @param async If {@code true} uses async method. * @throws Exception If failed. */ @Override protected void globalClearAll(boolean async, boolean oldAsync) throws Exception { // Save entries only on their primary nodes. If we didn't do so, clearLocally() will not remove all entries // because some of them were blocked due to having readers. for (int i = 0; i < gridCount(); i++) { if (i != nearIdx) { for (String key : primaryKeysForCache(jcache(i), 3, 100_000)) jcache(i).put(key, 1); } } if (async) { if (oldAsync) { IgniteCache<String, Integer> asyncCache = jcache(nearIdx).withAsync(); asyncCache.clear(); asyncCache.future().get(); } else jcache(nearIdx).clearAsync().get(); } else jcache(nearIdx).clear(); for (int i = 0; i < gridCount(); i++) { assertEquals("Unexpected size [node=" + ignite(i).name() + ", nearIdx=" + nearIdx + ']', 0, jcache(i).localSize()); } } /** {@inheritDoc} */ @SuppressWarnings("BusyWait") @Override public void testLockUnlock() throws Exception { if (lockingEnabled()) { final CountDownLatch lockCnt = new CountDownLatch(1); final CountDownLatch unlockCnt = new CountDownLatch(1); grid(0).events().localListen(new IgnitePredicate<Event>() { @Override public boolean apply(Event evt) { switch (evt.type()) { case EVT_CACHE_OBJECT_LOCKED: lockCnt.countDown(); break; case EVT_CACHE_OBJECT_UNLOCKED: unlockCnt.countDown(); break; } return true; } }, EVT_CACHE_OBJECT_LOCKED, EVT_CACHE_OBJECT_UNLOCKED); IgniteCache<String, Integer> nearCache = jcache(); IgniteCache<String, Integer> cache = fullCache(); String key = primaryKeysForCache(cache, 1).get(0); nearCache.put(key, 1); assert !nearCache.isLocalLocked(key, false); assert !cache.isLocalLocked(key, false); Lock lock = nearCache.lock(key); lock.lock(); try { lockCnt.await(); assert nearCache.isLocalLocked(key, false); assert cache.isLocalLocked(key, false); } finally { lock.unlock(); } unlockCnt.await(); for (int i = 0; i < 100; i++) { if (cache.isLocalLocked(key, false)) Thread.sleep(10); else break; } assert !nearCache.isLocalLocked(key, false); assert !cache.isLocalLocked(key, false); } } }