/* * 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; import com.hazelcast.config.Config; import com.hazelcast.core.EntryView; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import com.hazelcast.core.LifecycleEvent; import com.hazelcast.core.LifecycleListener; import com.hazelcast.map.merge.MapMergePolicy; import com.hazelcast.nio.ObjectDataInput; import com.hazelcast.nio.ObjectDataOutput; import com.hazelcast.test.HazelcastParallelClassRunner; import com.hazelcast.test.SplitBrainTestSupport; import com.hazelcast.test.annotation.ParallelTest; import com.hazelcast.test.annotation.QuickTest; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; /** * Given: * 3-members cluster, maps configured with custom merge policy that subtracts merging from existing value (if exists) * or the merging value itself * * When: * cluster splits in two subclusters with {1, 2} members respectively, on each brain put values: * on first brain, keys 0..1999 -> value 1 * on second brain, keys 1000..2999 -> value 3 * Then: * custom merge policy's merge method is invoked for all entries of the map, assert final map values as follows: * keys 0..999 -> value 1 (merged, no existing value) * keys 1000..1999 -> value 2 (merged, result of (3-1)) * keys 2000..2999 -> value 3 (not merged) */ @RunWith(HazelcastParallelClassRunner.class) @Category({QuickTest.class, ParallelTest.class}) public class MapSplitBrainTest extends SplitBrainTestSupport { private static final CopyOnWriteArrayList<SubtractingMergePolicy> MERGE_POLICY_INSTANCES = new CopyOnWriteArrayList<SubtractingMergePolicy>(); private static final String TEST_MAPS_PREFIX = "MapSplitBrainTest"; private String testMapName; final CountDownLatch clusterMergedLatch = new CountDownLatch(1); final AtomicInteger countOfMerges = new AtomicInteger(); @Override protected Config config() { Config config = super.config(); config.getMapConfig(TEST_MAPS_PREFIX + "*") .setMergePolicy(SubtractingMergePolicy.class.getName()); return config; } @Override protected void onBeforeSplitBrainCreated(HazelcastInstance[] instances) throws Exception { testMapName = TEST_MAPS_PREFIX + randomMapName(); instances[0].getLifecycleService().addLifecycleListener(new MergedLifecycleListener()); } @Override protected void onAfterSplitBrainCreated(HazelcastInstance[] firstBrain, HazelcastInstance[] secondBrain) throws Exception { // put value 1 for keys 0..1999 on first brain IMap<Integer, Integer> mapOnFirstBrain = firstBrain[0].getMap(testMapName); for (int i = 0; i < 2000; i++) { mapOnFirstBrain.put(i, 1); } // put value 3 for keys 1000..2999 on second brain IMap<Integer, Integer> mapOnSecondBrain = secondBrain[0].getMap(testMapName); for (int i = 1000; i < 3000; i++) { mapOnSecondBrain.put(i, 3); } } @Override protected void onAfterSplitBrainHealed(HazelcastInstance[] instances) throws Exception { assertOpenEventually(clusterMergedLatch, 30); // Map on smaller, merging cluster has 2000 entries (0..1999), so 2000 merges should have happened assertEquals(2000, countOfMerges.get()); IMap<Integer, Integer> map = instances[0].getMap(testMapName); // final map should have: // keys 0..999: value 1 // keys 1000..1999: value 2 // keys 2000..2999: value 3 for (int i = 0; i < 3000; i++) { try { assertEquals(i / 1000 + 1, (long) map.get(i)); } catch (Exception e) { System.out.println(">>>> " + i); e.printStackTrace(); } } } /** * a merge policy that subtracts the integer value of merging entry from the existing entry (if one exists) */ public static class SubtractingMergePolicy implements MapMergePolicy { final AtomicInteger counter; public SubtractingMergePolicy() { this.counter = new AtomicInteger(); MERGE_POLICY_INSTANCES.add(this); } @Override public void writeData(ObjectDataOutput out) throws IOException { } @Override public void readData(ObjectDataInput in) throws IOException { } @Override public Object merge(String mapName, EntryView mergingEntry, EntryView existingEntry) { counter.incrementAndGet(); Integer existingValue = (Integer) existingEntry.getValue(); Integer mergingValue = (Integer) mergingEntry.getValue(); if (existingValue != null) { return existingValue - mergingValue; } else { return mergingEntry.getValue(); } } } class MergedLifecycleListener implements LifecycleListener { @Override public void stateChanged(LifecycleEvent event) { if (event.getState() == LifecycleEvent.LifecycleState.MERGED) { for (SubtractingMergePolicy mergePolicy : MERGE_POLICY_INSTANCES) { countOfMerges.addAndGet(mergePolicy.counter.get()); } clusterMergedLatch.countDown(); } } } }