/*
* 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.wan.impl;
import com.hazelcast.config.Config;
import com.hazelcast.config.WanPublisherConfig;
import com.hazelcast.config.WanReplicationConfig;
import com.hazelcast.config.WanReplicationRef;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.map.EntryBackupProcessor;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.map.impl.MapService;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.operation.MapOperationProvider;
import com.hazelcast.map.impl.proxy.MapProxyImpl;
import com.hazelcast.map.merge.PassThroughMergePolicy;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.spi.OperationFactory;
import com.hazelcast.spi.impl.operationservice.InternalOperationService;
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.QuickTest;
import com.hazelcast.wan.WanReplicationEvent;
import com.hazelcast.wan.WanReplicationService;
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.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Tests for WAN replication API.
*/
@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelTest.class})
public class WanReplicationTest extends HazelcastTestSupport {
private TestHazelcastInstanceFactory factory;
private HazelcastInstance instance1;
private HazelcastInstance instance2;
private IMap<Object, Object> map;
private DummyWanReplication impl1;
private DummyWanReplication impl2;
@Before
public void setUp() {
factory = createHazelcastInstanceFactory(2);
}
@After
public void tearDown() {
factory.terminateAll();
}
@Test
public void mapPutReplaceRemoveTest() {
initInstancesAndMap("wan-replication-test-put-replace-remove");
for (int i = 0; i < 10; i++) {
map.put(i, i);
map.replace(i, i * 2);
map.remove(i);
}
assertTotalQueueSize(30);
}
@Test
public void mapSetReplaceRemoveIfSameTest() {
initInstancesAndMap("wan-replication-test-set-replace-remove-if-same");
for (int i = 0; i < 10; i++) {
map.set(i, i);
map.replace(i, i, i * 2);
map.remove(i, i * 2);
}
assertTotalQueueSize(30);
}
@Test
public void mapTryPutRemoveTest() {
initInstancesAndMap("wan-replication-test-try-put-remove");
for (int i = 0; i < 10; i++) {
assertTrue(map.tryPut(i, i, 10, TimeUnit.SECONDS));
assertTrue(map.tryRemove(i, 10, TimeUnit.SECONDS));
}
assertTotalQueueSize(20);
}
@Test
public void mapPutIfAbsentDeleteTest() {
initInstancesAndMap("wan-replication-test-put-if-absent-delete");
for (int i = 0; i < 10; i++) {
assertNull(map.putIfAbsent(i, i));
map.delete(i);
}
assertTotalQueueSize(20);
}
@Test
public void mapPutTransientTest() {
initInstancesAndMap("wan-replication-test-put-transient");
for (int i = 0; i < 10; i++) {
map.putTransient(i, i, 1, TimeUnit.SECONDS);
}
assertTotalQueueSize(10);
}
@Test
public void mapPutAllTest() {
Map<Object, Object> userMap = new HashMap<Object, Object>();
for (int i = 0; i < 10; i++) {
userMap.put(i, i);
}
initInstancesAndMap("wan-replication-test-put-all");
map.putAll(userMap);
assertTotalQueueSize(10);
}
@Test
public void entryProcessorTest() throws Exception {
initInstancesAndMap("wan-replication-test-entry-processor");
for (int i = 0; i < 10; i++) {
map.put(i, i);
}
assertTotalQueueSize(10);
// clean event queues
impl1.eventQueue.clear();
impl2.eventQueue.clear();
InternalSerializationService serializationService = getSerializationService(instance1);
Set<Data> keySet = new HashSet<Data>();
for (int i = 0; i < 10; i++) {
keySet.add(serializationService.toData(i));
}
// multiple entry operations (update)
OperationFactory operationFactory = getOperationProvider(map).createMultipleEntryOperationFactory(map.getName(), keySet,
new UpdatingEntryProcessor());
InternalOperationService operationService = getOperationService(instance1);
operationService.invokeOnAllPartitions(MapService.SERVICE_NAME, operationFactory);
// there should be 10 events since all entries should be processed
assertTotalQueueSize(10);
// multiple entry operations (remove)
OperationFactory deletingOperationFactory = getOperationProvider(map).createMultipleEntryOperationFactory(map.getName(),
keySet, new DeletingEntryProcessor());
operationService.invokeOnAllPartitions(MapService.SERVICE_NAME, deletingOperationFactory);
// 10 more event should be published
assertTotalQueueSize(20);
}
@Test
public void programmaticImplCreationTest() {
Config config = getConfig();
WanPublisherConfig publisherConfig = config.getWanReplicationConfig("dummyWan").getWanPublisherConfigs().get(0);
DummyWanReplication dummyWanReplication = new DummyWanReplication();
publisherConfig.setImplementation(dummyWanReplication);
instance1 = factory.newHazelcastInstance(config);
assertEquals(dummyWanReplication, getWanReplicationImpl(instance1));
}
private void initInstancesAndMap(String name) {
instance1 = factory.newHazelcastInstance(getConfig());
instance2 = factory.newHazelcastInstance(getConfig());
map = instance1.getMap(name);
}
@Override
protected Config getConfig() {
Config config = new Config();
WanReplicationConfig wanConfig = new WanReplicationConfig();
wanConfig.setName("dummyWan");
wanConfig.addWanPublisherConfig(getPublisherConfig());
WanReplicationRef wanRef = new WanReplicationRef();
wanRef.setName("dummyWan");
wanRef.setMergePolicy(PassThroughMergePolicy.class.getName());
config.addWanReplicationConfig(wanConfig);
config.getMapConfig("default").setWanReplicationRef(wanRef);
return config;
}
private WanPublisherConfig getPublisherConfig() {
WanPublisherConfig publisherConfig = new WanPublisherConfig();
publisherConfig.setClassName(DummyWanReplication.class.getName());
return publisherConfig;
}
private DummyWanReplication getWanReplicationImpl(HazelcastInstance instance) {
WanReplicationService service = getNodeEngineImpl(instance).getWanReplicationService();
WanReplicationPublisherDelegate delegate = (WanReplicationPublisherDelegate) service.getWanReplicationPublisher("dummyWan");
return (DummyWanReplication) delegate.getEndpoints()[0];
}
private MapOperationProvider getOperationProvider(Map map) {
MapProxyImpl mapProxy = (MapProxyImpl) map;
MapServiceContext mapServiceContext = ((MapService) mapProxy.getService()).getMapServiceContext();
return mapServiceContext.getMapOperationProvider(mapProxy.getName());
}
private void assertTotalQueueSize(final int expectedQueueSize) {
if (impl1 == null) {
impl1 = getWanReplicationImpl(instance1);
impl2 = getWanReplicationImpl(instance2);
}
final Queue<WanReplicationEvent> eventQueue1 = impl1.eventQueue;
final Queue<WanReplicationEvent> eventQueue2 = impl2.eventQueue;
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(expectedQueueSize, eventQueue1.size() + eventQueue2.size());
}
});
}
private static class UpdatingEntryProcessor implements EntryProcessor<Object, Object>, EntryBackupProcessor<Object, Object> {
@Override
public Object process(Map.Entry<Object, Object> entry) {
entry.setValue("EP" + entry.getValue());
return "done";
}
@Override
public EntryBackupProcessor<Object, Object> getBackupProcessor() {
return this;
}
@Override
public void processBackup(Map.Entry<Object, Object> entry) {
process(entry);
}
}
private static class DeletingEntryProcessor implements EntryProcessor<Object, Object>, EntryBackupProcessor<Object, Object> {
@Override
public Object process(Map.Entry<Object, Object> entry) {
entry.setValue(null);
return "done";
}
@Override
public EntryBackupProcessor<Object, Object> getBackupProcessor() {
return this;
}
@Override
public void processBackup(Map.Entry<Object, Object> entry) {
process(entry);
}
}
}