/* * 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; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.partition.AbstractPartitionLostListenerTest; import com.hazelcast.test.AssertTask; import com.hazelcast.test.HazelcastParametersRunnerFactory; import com.hazelcast.test.annotation.SlowTest; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @RunWith(Parameterized.class) @Parameterized.UseParametersRunnerFactory(HazelcastParametersRunnerFactory.class) @Category(SlowTest.class) public class MapPartitionLostListenerStressTest extends AbstractPartitionLostListenerTest { @Parameters(name = "numberOfNodesToCrash:{0},withData:{1},nodeLeaveType:{2},shouldExpectPartitionLostEvents:{3}") public static Collection<Object[]> parameters() { return Arrays.asList(new Object[][]{ {1, true, NodeLeaveType.SHUTDOWN, false}, {1, true, NodeLeaveType.TERMINATE, true}, {1, false, NodeLeaveType.SHUTDOWN, false}, {1, false, NodeLeaveType.TERMINATE, true}, {2, true, NodeLeaveType.SHUTDOWN, false}, {2, true, NodeLeaveType.TERMINATE, true}, {2, false, NodeLeaveType.SHUTDOWN, false}, {2, false, NodeLeaveType.TERMINATE, true}, {3, true, NodeLeaveType.SHUTDOWN, false}, {3, true, NodeLeaveType.TERMINATE, true}, {3, false, NodeLeaveType.SHUTDOWN, false}, {3, false, NodeLeaveType.TERMINATE, true} }); } @Parameter(0) public int numberOfNodesToCrash; @Parameter(1) public boolean withData; @Parameter(2) public NodeLeaveType nodeLeaveType; @Parameter(3) public boolean shouldExpectPartitionLostEvents; @Override protected int getNodeCount() { return 5; } @Override protected int getMapEntryCount() { return 5000; } @Test public void testMapPartitionLostListener() throws InterruptedException { List<HazelcastInstance> instances = getCreatedInstancesShuffledAfterWarmedUp(); List<HazelcastInstance> survivingInstances = new ArrayList<HazelcastInstance>(instances); List<HazelcastInstance> terminatingInstances = survivingInstances.subList(0, numberOfNodesToCrash); survivingInstances = survivingInstances.subList(numberOfNodesToCrash, instances.size()); List<TestEventCollectingMapPartitionLostListener> listeners = registerListeners(survivingInstances.get(0)); if (withData) { populateMaps(survivingInstances.get(0)); } String log = "Surviving: " + survivingInstances + " Terminating: " + terminatingInstances; Map<Integer, Integer> survivingPartitions = getMinReplicaIndicesByPartitionId(survivingInstances); stopInstances(terminatingInstances, nodeLeaveType); waitAllForSafeStateAndDumpPartitionServiceOnFailure(survivingInstances, 300); if (shouldExpectPartitionLostEvents) { for (int i = 0; i < getNodeCount(); i++) { assertListenerInvocationsEventually(log, i, numberOfNodesToCrash, listeners.get(i), survivingPartitions); } } else { for (final TestEventCollectingMapPartitionLostListener listener : listeners) { assertTrueAllTheTime(new AssertTask() { @Override public void run() throws Exception { assertTrue(listener.getEvents().isEmpty()); } }, 1); } } } private List<TestEventCollectingMapPartitionLostListener> registerListeners(HazelcastInstance instance) { List<TestEventCollectingMapPartitionLostListener> listeners = new ArrayList<TestEventCollectingMapPartitionLostListener>(); for (int i = 0; i < getNodeCount(); i++) { TestEventCollectingMapPartitionLostListener listener = new TestEventCollectingMapPartitionLostListener(i); instance.getMap(getIthMapName(i)).addPartitionLostListener(listener); listeners.add(listener); } return listeners; } private static void assertLostPartitions(String log, TestEventCollectingMapPartitionLostListener listener, Map<Integer, Integer> survivingPartitions) { List<MapPartitionLostEvent> events = listener.getEvents(); assertFalse(survivingPartitions.isEmpty()); for (MapPartitionLostEvent event : events) { int failedPartitionId = event.getPartitionId(); Integer survivingReplicaIndex = survivingPartitions.get(failedPartitionId); if (survivingReplicaIndex != null) { String message = log + ", PartitionId: " + failedPartitionId + " SurvivingReplicaIndex: " + survivingReplicaIndex + " Event: " + event.toString(); assertTrue(message, survivingReplicaIndex > listener.getBackupCount()); } } } private static void assertListenerInvocationsEventually(final String log, final int index, final int numberOfNodesToCrash, final TestEventCollectingMapPartitionLostListener listener, final Map<Integer, Integer> survivingPartitions) { assertTrueEventually(new AssertTask() { @Override public void run() throws Exception { if (index < numberOfNodesToCrash) { assertLostPartitions(log, listener, survivingPartitions); } else { String message = log + " listener-" + index + " should not be invoked!"; assertTrue(message, listener.getEvents().isEmpty()); } } }); } }