/* * 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.client.map; import com.hazelcast.client.map.helpers.AMapStore; import com.hazelcast.client.test.TestHazelcastFactory; import com.hazelcast.config.Config; import com.hazelcast.config.MapConfig; import com.hazelcast.config.MapStoreConfig; import com.hazelcast.config.XmlConfigBuilder; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import com.hazelcast.core.MapLoader; import com.hazelcast.core.MapStore; import com.hazelcast.map.ReachedMaxSizeException; 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.annotation.SlowTest; 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.io.ByteArrayInputStream; import java.util.ArrayList; 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.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @RunWith(HazelcastParallelClassRunner.class) @Category(SlowTest.class) public class ClientMapStoreTest extends HazelcastTestSupport { private static final String MAP_NAME = "clientMapStoreLoad"; private TestHazelcastFactory hazelcastFactory = new TestHazelcastFactory(); private Config nodeConfig; @After public void tearDown() { hazelcastFactory.terminateAll(); } @Before public void setup() { nodeConfig = new Config(); MapConfig mapConfig = new MapConfig(); MapStoreConfig mapStoreConfig = new MapStoreConfig(); mapStoreConfig.setEnabled(true); mapStoreConfig.setImplementation(new SimpleMapStore()); mapStoreConfig.setInitialLoadMode(MapStoreConfig.InitialLoadMode.EAGER); mapConfig.setName(MAP_NAME); mapConfig.setMapStoreConfig(mapStoreConfig); nodeConfig.addMapConfig(mapConfig); } @Test public void testOneClient_KickOffMapStoreLoad() { hazelcastFactory.newHazelcastInstance(nodeConfig); ClientThread client1 = new ClientThread(); client1.start(); assertJoinable(client1); assertSizeEventually(SimpleMapStore.MAX_KEYS, client1.map); } @Test public void testTwoClient_KickOffMapStoreLoad() { hazelcastFactory.newHazelcastInstance(nodeConfig); ClientThread[] clientThreads = new ClientThread[2]; for (int i = 0; i < clientThreads.length; i++) { ClientThread client1 = new ClientThread(); client1.start(); clientThreads[i] = client1; } assertJoinable(clientThreads); for (ClientThread c : clientThreads) { assertSizeEventually(SimpleMapStore.MAX_KEYS, c.map); } } @Test public void testOneClientKickOffMapStoreLoad_ThenNodeJoins() { hazelcastFactory.newHazelcastInstance(nodeConfig); ClientThread client1 = new ClientThread(); client1.start(); hazelcastFactory.newHazelcastInstance(nodeConfig); assertJoinable(client1); assertSizeEventually(SimpleMapStore.MAX_KEYS, client1.map); } @Test public void testForIssue2112() { hazelcastFactory.newHazelcastInstance(nodeConfig); IMap<String, String> map = hazelcastFactory.newHazelcastClient().getMap(ClientMapStoreTest.MAP_NAME); assertSizeEventually(SimpleMapStore.MAX_KEYS, map); hazelcastFactory.newHazelcastInstance(nodeConfig); map = hazelcastFactory.newHazelcastClient().getMap(ClientMapStoreTest.MAP_NAME); assertSizeEventually(SimpleMapStore.MAX_KEYS, map); } @Test public void mapSize_After_MapStore_OperationQueue_OverFlow() throws Exception { int maxCapacity = 1000; Config config = new Config(); config.setProperty(GroupProperty.MAP_WRITE_BEHIND_QUEUE_CAPACITY.getName(), String.valueOf(maxCapacity)); MapConfig mapConfig = new MapConfig(); MapStoreConfig mapStoreConfig = new MapStoreConfig(); MapStoreBackup store = new MapStoreBackup(); int delaySeconds = Integer.MAX_VALUE; mapStoreConfig.setEnabled(true); mapStoreConfig.setImplementation(store); mapStoreConfig.setWriteDelaySeconds(delaySeconds); mapStoreConfig.setWriteCoalescing(false); mapConfig.setName(MAP_NAME); mapConfig.setMapStoreConfig(mapStoreConfig); config.addMapConfig(mapConfig); HazelcastInstance server = hazelcastFactory.newHazelcastInstance(config); HazelcastInstance client = hazelcastFactory.newHazelcastClient(); IMap<Integer, Integer> map = client.getMap(MAP_NAME); int overflow = 100; List<Future> futures = new ArrayList<Future>(maxCapacity + overflow); for (int i = 0; i < maxCapacity + overflow; i++) { Future future = map.putAsync(i, i); futures.add(future); } int success = 0; for (Future future : futures) { try { future.get(); success++; } catch (ExecutionException e) { assertInstanceOf(ReachedMaxSizeException.class, e.getCause()); } } assertEquals(success, maxCapacity); assertEquals(map.size(), maxCapacity); } @Test public void mapStore_OperationQueue_AtMaxCapacity() { int maxCapacity = 1000; Config config = new Config(); config.setProperty(GroupProperty.MAP_WRITE_BEHIND_QUEUE_CAPACITY.getName(), String.valueOf(maxCapacity)); MapConfig mapConfig = new MapConfig(); MapStoreConfig mapStoreConfig = new MapStoreConfig(); MapStoreBackup store = new MapStoreBackup(); mapStoreConfig.setEnabled(true); mapStoreConfig.setImplementation(store); mapStoreConfig.setWriteDelaySeconds(60); mapStoreConfig.setWriteCoalescing(false); mapConfig.setName(MAP_NAME); mapConfig.setMapStoreConfig(mapStoreConfig); config.addMapConfig(mapConfig); HazelcastInstance server = hazelcastFactory.newHazelcastInstance(config); HazelcastInstance client = hazelcastFactory.newHazelcastClient(); IMap<Integer, Integer> map = client.getMap(MAP_NAME); for (int i = 0; i < maxCapacity; i++) { map.put(i, i); } assertEquals(maxCapacity, map.size()); try { map.put(maxCapacity, maxCapacity); fail("Should not exceed max capacity"); } catch (ReachedMaxSizeException expected) { ignore(expected); } } @Test public void destroyMap_configuredWithMapStore() { Config config = new Config(); MapConfig mapConfig = new MapConfig(); MapStoreConfig mapStoreConfig = new MapStoreConfig(); MapStoreBackup store = new MapStoreBackup(); mapStoreConfig.setEnabled(true); mapStoreConfig.setImplementation(store); mapStoreConfig.setWriteDelaySeconds(4); mapConfig.setName(MAP_NAME); mapConfig.setMapStoreConfig(mapStoreConfig); config.addMapConfig(mapConfig); HazelcastInstance server = hazelcastFactory.newHazelcastInstance(config); HazelcastInstance client = hazelcastFactory.newHazelcastClient(); IMap<Integer, Integer> map = client.getMap(MAP_NAME); for (int i = 0; i < 1; i++) { map.putAsync(i, i); } map.destroy(); } static class SimpleMapStore implements MapStore<String, String>, MapLoader<String, String> { static final int MAX_KEYS = 30; static final int DELAY_SECONDS_PER_KEY = 1; @Override public String load(String key) { sleepSeconds(DELAY_SECONDS_PER_KEY); return key + "value"; } @Override public Map<String, String> loadAll(Collection<String> keys) { Map<String, String> map = new HashMap<String, String>(); for (String key : keys) { map.put(key, load(key)); } return map; } @Override public Set<String> loadAllKeys() { Set<String> keys = new HashSet<String>(); for (int i = 0; i < MAX_KEYS; i++) { keys.add("key" + i); } return keys; } @Override public void delete(String key) { sleepSeconds(DELAY_SECONDS_PER_KEY); } @Override public void deleteAll(Collection<String> keys) { for (String key : keys) { delete(key); } } @Override public void store(String key, String value) { sleepSeconds(DELAY_SECONDS_PER_KEY); } @Override public void storeAll(Map<String, String> entries) { for (Map.Entry<String, String> e : entries.entrySet()) { store(e.getKey(), e.getValue()); } } } private class ClientThread extends Thread { IMap<String, String> map; @Override public void run() { HazelcastInstance client = hazelcastFactory.newHazelcastClient(); map = client.getMap(ClientMapStoreTest.MAP_NAME); map.size(); } } public class MapStoreBackup implements MapStore<Object, Object> { public final Map<Object, Object> store = new ConcurrentHashMap<Object, Object>(); @Override public void store(Object key, Object value) { store.put(key, value); } @Override public void storeAll(Map<Object, Object> map) { for (Map.Entry<Object, Object> kvp : map.entrySet()) { store.put(kvp.getKey(), kvp.getValue()); } } @Override public void delete(Object key) { store.remove(key); } @Override public void deleteAll(Collection<Object> keys) { for (Object key : keys) { store.remove(key); } } @Override public Object load(Object key) { return store.get(key); } @Override public Map<Object, Object> loadAll(Collection<Object> keys) { Map<Object, Object> result = new HashMap<Object, Object>(); for (Object key : keys) { Object v = store.get(key); if (v != null) { result.put(key, v); } } return result; } @Override public Set<Object> loadAllKeys() { return store.keySet(); } } @Test public void testIssue3023_testWithSubStringMapNames() throws Exception { String mapNameWithStore = "MapStore*"; String mapNameWithStoreAndSize = "MapStoreMaxSize*"; String xml = "<hazelcast xsi:schemaLocation=\"http://www.hazelcast.com/schema/config\n" + " http://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd\"\n" + " xmlns=\"http://www.hazelcast.com/schema/config\"\n" + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" + "\n" + " <map name=\"" + mapNameWithStore + "\">\n" + " <map-store enabled=\"true\">\n" + " <class-name>com.will.cause.problem.if.used</class-name>\n" + " <write-delay-seconds>5</write-delay-seconds>\n" + " </map-store>\n" + " </map>\n" + "\n" + " <map name=\"" + mapNameWithStoreAndSize + "\">\n" + " <in-memory-format>BINARY</in-memory-format>\n" + " <backup-count>1</backup-count>\n" + " <async-backup-count>0</async-backup-count>\n" + " <max-idle-seconds>0</max-idle-seconds>\n" + " <eviction-policy>LRU</eviction-policy>\n" + " <max-size policy=\"PER_NODE\">10</max-size>\n" + " <eviction-percentage>50</eviction-percentage>\n" + "\n" + " <merge-policy>com.hazelcast.map.merge.PassThroughMergePolicy</merge-policy>\n" + "\n" + " <map-store enabled=\"true\">\n" + " <class-name>com.hazelcast.client.map.helpers.AMapStore</class-name>\n" + " <write-delay-seconds>5</write-delay-seconds>\n" + " </map-store>\n" + " </map>\n" + "\n" + "</hazelcast>"; Config config = buildConfig(xml); HazelcastInstance hz = hazelcastFactory.newHazelcastInstance(config); HazelcastInstance client = hazelcastFactory.newHazelcastClient(); IMap<Integer, Integer> map = client.getMap(mapNameWithStoreAndSize + "1"); map.put(1, 1); MapStoreConfig mapStoreConfig = hz.getConfig() .getMapConfig(mapNameWithStoreAndSize + "1") .getMapStoreConfig(); final AMapStore store = (AMapStore) (mapStoreConfig.getImplementation()); assertTrueEventually(new AssertTask() { @Override public void run() throws Exception { assertEquals(1, store.store.get(1)); } }); } private Config buildConfig(String xml) { ByteArrayInputStream bis = new ByteArrayInputStream(xml.getBytes()); XmlConfigBuilder configBuilder = new XmlConfigBuilder(bis); return configBuilder.build(); } }