/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed 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 com.hazelcast.map.impl.query; import com.hazelcast.config.Config; import com.hazelcast.config.MapIndexConfig; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import com.hazelcast.query.Predicate; import com.hazelcast.query.SampleObjects.Employee; import com.hazelcast.query.SampleObjects.Value; import com.hazelcast.query.SqlPredicate; import com.hazelcast.spi.properties.GroupProperty; import com.hazelcast.test.AssertTask; import com.hazelcast.test.HazelcastParallelClassRunner; import com.hazelcast.test.HazelcastTestSupport; import com.hazelcast.test.TestHazelcastInstanceFactory; import com.hazelcast.test.annotation.ParallelTest; import com.hazelcast.test.annotation.SlowTest; import com.hazelcast.util.Clock; import com.hazelcast.util.IterableUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static com.hazelcast.query.Predicates.equal; import static com.hazelcast.test.TimeConstants.MINUTE; import static java.lang.Thread.interrupted; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @RunWith(HazelcastParallelClassRunner.class) @Category({SlowTest.class, ParallelTest.class}) public class QueryIndexMigrationTest extends HazelcastTestSupport { private Random random = new Random(); private TestHazelcastInstanceFactory nodeFactory; private ExecutorService executor; @Before public void createFactory() { nodeFactory = createHazelcastInstanceFactory(6); } @After public void shutdown() throws Exception { if (executor != null) { executor.shutdownNow(); executor.awaitTermination(ASSERT_TRUE_EVENTUALLY_TIMEOUT, SECONDS); } shutdownNodeFactory(); } @Test(timeout = MINUTE) public void testQueryDuringAndAfterMigration() throws Exception { HazelcastInstance instance = nodeFactory.newHazelcastInstance(); int count = 500; IMap<String, Employee> map = instance.getMap("employees"); for (int i = 0; i < count; i++) { map.put(String.valueOf(i), new Employee("joe" + i, i % 60, ((i & 1) == 1), (double) i)); } nodeFactory.newInstances(new Config(), 3); final IMap<String, Employee> employees = instance.getMap("employees"); assertTrueAllTheTime(new AssertTask() { @Override public void run() throws Exception { Collection<Employee> values = employees.values(new SqlPredicate("active and name LIKE 'joe15%'")); for (Employee employee : values) { assertTrue(employee.isActive()); } assertEquals(6, values.size()); } }, 3); } @Test public void testQueryDuringAndAfterMigrationWithIndex() throws Exception { Config config = new Config(); HazelcastInstance instance = nodeFactory.newHazelcastInstance(config); IMap<String, Employee> map = instance.getMap("employees"); map.addIndex("name", false); map.addIndex("active", false); int size = 500; for (int i = 0; i < size; i++) { map.put(String.valueOf(i), new Employee("joe" + i, i % 60, ((i & 1) == 1), (double) i)); } nodeFactory.newInstances(config, 3); final IMap<String, Employee> employees = instance.getMap("employees"); assertTrueAllTheTime(new AssertTask() { @Override public void run() throws Exception { Collection<Employee> values = employees.values(new SqlPredicate("active and name LIKE 'joe15%'")); for (Employee employee : values) { assertTrue(employee.isActive() && employee.getName().startsWith("joe15")); } assertEquals(6, values.size()); } }, 3); } @Test public void testQueryWithIndexesWhileMigrating() throws Exception { HazelcastInstance instance = nodeFactory.newHazelcastInstance(); IMap<String, Employee> map = instance.getMap("employees"); map.addIndex("age", true); map.addIndex("active", false); for (int i = 0; i < 500; i++) { map.put("e" + i, new Employee("name" + i, i % 50, ((i & 1) == 1), (double) i)); } assertEquals(500, map.size()); Set<Map.Entry<String, Employee>> entries = map.entrySet(new SqlPredicate("active=true and age>44")); assertEquals(30, entries.size()); nodeFactory.newInstances(new Config(), 3); long startNow = Clock.currentTimeMillis(); while ((Clock.currentTimeMillis() - startNow) < 10000) { entries = map.entrySet(new SqlPredicate("active=true and age>44")); assertEquals(30, entries.size()); } } /** * test for issue #359 */ @Test(timeout = 4 * MINUTE) public void testIndexCleanupOnMigration() throws Exception { int nodeCount = 6; final int runCount = 500; final Config config = newConfigWithIndex("testMap", "name"); executor = Executors.newFixedThreadPool(nodeCount); List<Future<?>> futures = new ArrayList<Future<?>>(); for (int i = 0; i < nodeCount; i++) { sleepMillis(random.nextInt((i + 1) * 100) + 10); futures.add(executor.submit(new Runnable() { public void run() { HazelcastInstance hz = nodeFactory.newHazelcastInstance(config); IMap<Object, Value> map = hz.getMap("testMap"); updateMapAndRunQuery(map, runCount); } })); } for (Future<?> future : futures) { future.get(); } } private Config newConfigWithIndex(String mapName, String attribute) { Config config = new Config(); config.setProperty(GroupProperty.WAIT_SECONDS_BEFORE_JOIN.getName(), "0"); config.getMapConfig(mapName).addMapIndexConfig(new MapIndexConfig(attribute, false)); return config; } /** * see Zendesk ticket #82 */ @Test(timeout = MINUTE) public void testQueryWithIndexDuringJoin() throws InterruptedException { final String name = "test"; final String findMe = "find-me"; int nodeCount = 5; final int entryPerNode = 1000; final int modulo = 10; final CountDownLatch latch = new CountDownLatch(nodeCount); final Config config = newConfigWithIndex(name, "name"); for (int i = 0; i < nodeCount; i++) { new Thread(new Runnable() { @Override public void run() { HazelcastInstance hz = nodeFactory.newHazelcastInstance(config); IMap<Object, Object> map = hz.getMap(name); fillMap(map, findMe, entryPerNode, modulo); latch.countDown(); } }).start(); } assertTrue(latch.await(1, MINUTES)); Collection<HazelcastInstance> instances = nodeFactory.getAllHazelcastInstances(); assertEquals(nodeCount, instances.size()); waitAllForSafeState(instances); int expected = entryPerNode / modulo * nodeCount; for (HazelcastInstance hz : instances) { IMap<Object, Object> map = hz.getMap(name); Predicate predicate = equal("name", findMe); for (int i = 0; i < 10; i++) { int size = map.values(predicate).size(); assertEquals(expected, size); } } } private void updateMapAndRunQuery(final IMap<Object, Value> map, final int runCount) { String name = randomString(); Predicate<?, ?> predicate = equal("name", name); map.put(name, new Value(name, 0)); // helper call on nodes to sync partitions (see issue github.com/hazelcast/hazelcast/issues/1282) map.size(); for (int i = 1; i <= runCount && !interrupted(); i++) { Value value = map.get(name); value.setIndex(i); map.put(name, value); sleepMillis(random.nextInt(100) + 1); Collection<Value> values = map.values(predicate); assertEquals(1, values.size()); Value firstValue = IterableUtil.getFirst(values, null); assertEquals(value, firstValue); } } private static void fillMap(IMap<Object, Object> map, final String value, final int count, final int modulo) { for (int i = 0; i < count; i++) { String name = randomString(); if (i % modulo == 0) { name = value; } map.put(randomString(), new Value(name, i)); } } }