/* * 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.internal.partition; import com.hazelcast.cluster.ClusterState; import com.hazelcast.config.Config; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.instance.Node; import com.hazelcast.internal.partition.impl.InternalPartitionServiceImpl; import com.hazelcast.nio.Address; import com.hazelcast.spi.properties.GroupProperty; 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.util.RandomPicker; 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.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import static com.hazelcast.instance.TestUtil.terminateInstance; import static com.hazelcast.internal.cluster.impl.AdvancedClusterStateTest.changeClusterStateEventually; import static com.hazelcast.internal.partition.InternalPartition.MAX_REPLICA_COUNT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @RunWith(HazelcastParallelClassRunner.class) @Category({QuickTest.class, ParallelTest.class}) public class GracefulShutdownTest extends HazelcastTestSupport { private TestHazelcastInstanceFactory factory; @Before public void setup() { factory = createHazelcastInstanceFactory(); } @Test public void shutdownSingleMember_withoutPartitionInitialization() { HazelcastInstance hz = factory.newHazelcastInstance(); hz.shutdown(); } @Test public void shutdownSingleMember_withPartitionInitialization() { HazelcastInstance hz = factory.newHazelcastInstance(); warmUpPartitions(hz); hz.shutdown(); } @Test public void shutdownSingleLiteMember() { HazelcastInstance hz = factory.newHazelcastInstance(new Config().setLiteMember(true)); hz.shutdown(); } @Test public void shutdownSlaveMember_withoutPartitionInitialization() { HazelcastInstance hz1 = factory.newHazelcastInstance(); HazelcastInstance hz2 = factory.newHazelcastInstance(); HazelcastInstance hz3 = factory.newHazelcastInstance(); hz2.shutdown(); } @Test public void shutdownSlaveMember_withPartitionInitialization() { HazelcastInstance hz1 = factory.newHazelcastInstance(); HazelcastInstance hz2 = factory.newHazelcastInstance(); HazelcastInstance hz3 = factory.newHazelcastInstance(); warmUpPartitions(hz1, hz2, hz3); hz2.shutdown(); assertPartitionAssignments(); } @Test public void shutdownSlaveMember_whilePartitionsMigrating() { Config config = new Config(); config.setProperty(GroupProperty.PARTITION_COUNT.getName(), "12"); config.setProperty(GroupProperty.PARTITION_MIGRATION_INTERVAL.getName(), "1"); HazelcastInstance hz1 = factory.newHazelcastInstance(config); warmUpPartitions(hz1); HazelcastInstance hz2 = factory.newHazelcastInstance(config); HazelcastInstance hz3 = factory.newHazelcastInstance(config); hz2.shutdown(); assertPartitionAssignments(); } @Test public void shutdownSlaveLiteMember() { HazelcastInstance hz1 = factory.newHazelcastInstance(); HazelcastInstance hz2 = factory.newHazelcastInstance(new Config().setLiteMember(true)); HazelcastInstance hz3 = factory.newHazelcastInstance(); warmUpPartitions(hz1, hz2, hz3); hz2.shutdown(); assertPartitionAssignments(); } @Test public void shutdownMasterMember_withoutPartitionInitialization() { HazelcastInstance hz1 = factory.newHazelcastInstance(); HazelcastInstance hz2 = factory.newHazelcastInstance(); HazelcastInstance hz3 = factory.newHazelcastInstance(); hz1.shutdown(); } @Test public void shutdownMasterMember_withPartitionInitialization() { HazelcastInstance hz1 = factory.newHazelcastInstance(); HazelcastInstance hz2 = factory.newHazelcastInstance(); HazelcastInstance hz3 = factory.newHazelcastInstance(); warmUpPartitions(hz1, hz2, hz3); hz1.shutdown(); assertPartitionAssignments(); } @Test public void shutdownMasterMember_whilePartitionsMigrating() { Config config = newConfig(); HazelcastInstance hz1 = factory.newHazelcastInstance(config); warmUpPartitions(hz1); HazelcastInstance hz2 = factory.newHazelcastInstance(config); HazelcastInstance hz3 = factory.newHazelcastInstance(config); hz1.shutdown(); assertPartitionAssignments(); } @Test public void shutdownMasterLiteMember() { HazelcastInstance hz1 = factory.newHazelcastInstance(new Config().setLiteMember(true)); HazelcastInstance hz2 = factory.newHazelcastInstance(); HazelcastInstance hz3 = factory.newHazelcastInstance(); warmUpPartitions(hz1, hz2, hz3); hz1.shutdown(); assertPartitionAssignments(); } @Test public void shutdownAllMembers_withoutPartitionInitialization() { shutdownAllMembers(false); } @Test public void shutdownAllMembers_withPartitionInitialization() { shutdownAllMembers(true); } private void shutdownAllMembers(boolean initializePartitions) { final HazelcastInstance[] instances = factory.newInstances(new Config(), 4); if (initializePartitions) { warmUpPartitions(instances); } final CountDownLatch latch = new CountDownLatch(instances.length); for (final HazelcastInstance instance : instances) { new Thread() { public void run() { instance.shutdown(); latch.countDown(); } }.start(); } assertOpenEventually(latch); } @Test public void shutdownMultipleSlaveMembers_withoutPartitionInitialization() { shutdownMultipleMembers(false, false); } @Test public void shutdownMultipleMembers_withoutPartitionInitialization() { shutdownMultipleMembers(true, false); } @Test public void shutdownMultipleSlaveMembers_withPartitionInitialization() { shutdownMultipleMembers(false, true); } @Test public void shutdownMultipleMembers_withPartitionInitialization() { shutdownMultipleMembers(true, true); } private void shutdownMultipleMembers(boolean includeMaster, boolean initializePartitions) { final HazelcastInstance[] instances = factory.newInstances(new Config(), 6); if (initializePartitions) { warmUpPartitions(instances); } final CountDownLatch latch = new CountDownLatch(instances.length / 2); int startIndex = includeMaster ? 0 : 1; for (int i = startIndex; i < instances.length; i += 2) { final int index = i; new Thread() { public void run() { instances[index].shutdown(); latch.countDown(); } }.start(); } assertOpenEventually(latch); if (initializePartitions) { assertPartitionAssignments(); } } @Test public void shutdownMultipleMembers_whilePartitionsMigrating() { Config config = newConfig(); HazelcastInstance master = factory.newHazelcastInstance(config); warmUpPartitions(master); HazelcastInstance[] slaves = factory.newInstances(config, 5); final List<HazelcastInstance> instances = new ArrayList<HazelcastInstance>(slaves.length + 1); instances.add(master); instances.addAll(Arrays.asList(slaves)); Collections.shuffle(instances); final int count = instances.size() / 2; final CountDownLatch latch = new CountDownLatch(count); for (int i = 0; i < count; i++) { final int index = i; new Thread() { public void run() { HazelcastInstance instance = instances.get(index); instance.shutdown(); latch.countDown(); } }.start(); } assertOpenEventually(latch); assertPartitionAssignments(); } @Test public void shutdownAndTerminateSlaveMembers_concurrently() { HazelcastInstance[] instances = factory.newInstances(new Config(), 5); int shutdownIndex = RandomPicker.getInt(1, instances.length); int terminateIndex; do { terminateIndex = RandomPicker.getInt(1, instances.length); } while (terminateIndex == shutdownIndex); shutdownAndTerminateMembers_concurrently(instances, shutdownIndex, terminateIndex); } @Test public void shutdownMasterAndTerminateSlaveMember_concurrently() { HazelcastInstance[] instances = factory.newInstances(new Config(), 5); int shutdownIndex = 0; int terminateIndex = RandomPicker.getInt(1, instances.length); shutdownAndTerminateMembers_concurrently(instances, shutdownIndex, terminateIndex); } @Test public void shutdownSlaveAndTerminateMasterMember_concurrently() { HazelcastInstance[] instances = factory.newInstances(new Config(), 5); int shutdownIndex = RandomPicker.getInt(1, instances.length); int terminateIndex = 0; shutdownAndTerminateMembers_concurrently(instances, shutdownIndex, terminateIndex); } private void shutdownAndTerminateMembers_concurrently(HazelcastInstance[] instances, int shutdownIndex, int terminateIndex) { warmUpPartitions(instances); final HazelcastInstance shuttingDownInstance = instances[shutdownIndex]; final CountDownLatch latch = new CountDownLatch(1); new Thread() { public void run() { shuttingDownInstance.shutdown(); latch.countDown(); } }.start(); // spin until node starts to shut down Node shuttingDownNode = getNode(shuttingDownInstance); while (shuttingDownNode.isRunning()) { ; } terminateInstance(instances[terminateIndex]); assertOpenEventually(latch); assertPartitionAssignments(); } @Test public void shutdownMasterMember_whenClusterFrozen_withoutPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(true, false, ClusterState.FROZEN); } @Test public void shutdownMasterMember_whenClusterPassive_withoutPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(true, false, ClusterState.PASSIVE); } @Test public void shutdownMasterMember_whenClusterFrozen_withPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(true, true, ClusterState.FROZEN); } @Test public void shutdownMasterMember_whenClusterPassive_withPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(true, true, ClusterState.PASSIVE); } @Test public void shutdownSlaveMember_whenClusterFrozen_withoutPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(false, false, ClusterState.FROZEN); } @Test public void shutdownSlaveMember_whenClusterPassive_withoutPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(false, false, ClusterState.PASSIVE); } @Test public void shutdownSlaveMember_whenClusterFrozen_withPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(false, true, ClusterState.FROZEN); } @Test public void shutdownSlaveMember_whenClusterPassive_withPartitionInitialization() throws Exception { shutdownMember_whenClusterNotActive(false, true, ClusterState.PASSIVE); } private void shutdownMember_whenClusterNotActive(boolean shutdownMaster, boolean initializePartitions, ClusterState state) throws Exception { Config config = new Config(); HazelcastInstance master = factory.newHazelcastInstance(config); HazelcastInstance[] slaves = factory.newInstances(config, 3); if (initializePartitions) { warmUpPartitions(slaves); } changeClusterStateEventually(slaves[0], state); InternalPartition[] partitionsBefore = getPartitionTable(master); if (shutdownMaster) { master.shutdown(); } else { slaves[0].shutdown(); } InternalPartition[] partitionsAfter = getPartitionTable(slaves[slaves.length - 1]); assertPartitionTableEquals(partitionsBefore, partitionsAfter); } @Test public void shutdownMemberAndCluster_withoutPartitionInitialization() throws Exception { shutdownMemberAndCluster(false); } @Test public void shutdownMemberAndCluster_withPartitionInitialization() throws Exception { shutdownMemberAndCluster(true); } private void shutdownMemberAndCluster(boolean initializePartitions) throws Exception { Config config = new Config(); HazelcastInstance master = factory.newHazelcastInstance(config); HazelcastInstance[] slaves = factory.newInstances(config, 3); if (initializePartitions) { warmUpPartitions(master); } master.shutdown(); changeClusterStateEventually(slaves[0], ClusterState.PASSIVE); slaves[0].getCluster().shutdown(); } @Test public void shutdownMemberAndCluster_concurrently_withoutPartitionInitialization() throws Exception { shutdownMemberAndCluster_concurrently(false); } @Test public void shutdownMemberAndCluster_concurrently_withPartitionInitialization() throws Exception { shutdownMemberAndCluster_concurrently(true); } private void shutdownMemberAndCluster_concurrently(boolean initializePartitions) throws Exception { Config config = new Config(); final HazelcastInstance master = factory.newHazelcastInstance(config); final HazelcastInstance[] slaves = factory.newInstances(config, 3); if (initializePartitions) { warmUpPartitions(master); } Future f1 = spawn(new Runnable() { @Override public void run() { master.shutdown(); } }); Future f2 = spawn(new Runnable() { @Override public void run() { changeClusterStateEventually(slaves[0], ClusterState.PASSIVE); slaves[0].getCluster().shutdown(); } }); f1.get(); f2.get(); } private InternalPartition[] getPartitionTable(HazelcastInstance instance) { InternalPartitionServiceImpl partitionService = getNode(instance).partitionService; return partitionService.getPartitionStateManager().getPartitionsCopy(); } private void assertPartitionTableEquals(InternalPartition[] partitions1, InternalPartition[] partitions2) { assertEquals(partitions1.length, partitions2.length); for (int i = 0; i < partitions1.length; i++) { assertPartitionEquals(partitions1[i], partitions2[i]); } } private void assertPartitionEquals(InternalPartition partition1, InternalPartition partition2) { for (int i = 0; i < MAX_REPLICA_COUNT; i++) { Address address1 = partition1.getReplicaAddress(i); Address address2 = partition2.getReplicaAddress(i); if (address1 == null) { assertNull(address2); } else { assertEquals(address1, address2); } } } private void assertPartitionAssignments() { PartitionAssignmentsCorrectnessTest.assertPartitionAssignments(factory); } private Config newConfig() { Config config = new Config(); config.setProperty(GroupProperty.PARTITION_COUNT.getName(), "6"); config.setProperty(GroupProperty.PARTITION_MIGRATION_INTERVAL.getName(), "1"); return config; } }