001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.master; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNotEquals; 022import static org.junit.Assert.assertNotNull; 023 024import java.io.IOException; 025import java.util.List; 026import java.util.concurrent.TimeUnit; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.ClusterMetrics; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseConfiguration; 031import org.apache.hadoop.hbase.HBaseTestingUtil; 032import org.apache.hadoop.hbase.LocalHBaseCluster; 033import org.apache.hadoop.hbase.SingleProcessHBaseCluster; 034import org.apache.hadoop.hbase.StartTestingClusterOption; 035import org.apache.hadoop.hbase.client.RetriesExhaustedException; 036import org.apache.hadoop.hbase.exceptions.ConnectionClosedException; 037import org.apache.hadoop.hbase.testclassification.LargeTests; 038import org.apache.hadoop.hbase.testclassification.MasterTests; 039import org.apache.hadoop.hbase.util.JVMClusterUtil.MasterThread; 040import org.apache.hadoop.hbase.zookeeper.ReadOnlyZKClient; 041import org.junit.Before; 042import org.junit.ClassRule; 043import org.junit.Test; 044import org.junit.experimental.categories.Category; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils; 049 050@Category({ MasterTests.class, LargeTests.class }) 051public class TestMasterShutdown { 052 private static final Logger LOG = LoggerFactory.getLogger(TestMasterShutdown.class); 053 054 @ClassRule 055 public static final HBaseClassTestRule CLASS_RULE = 056 HBaseClassTestRule.forClass(TestMasterShutdown.class); 057 058 private HBaseTestingUtil htu; 059 060 @Before 061 public void shutdownCluster() throws IOException { 062 if (htu != null) { 063 // an extra check in case the test cluster was not terminated after HBaseClassTestRule's 064 // Timeout interrupted the test thread. 065 LOG.warn("found non-null TestingUtility -- previous test did not terminate cleanly."); 066 htu.shutdownMiniCluster(); 067 } 068 } 069 070 /** 071 * Simple test of shutdown. 072 * <p> 073 * Starts with three masters. Tells the active master to shutdown the cluster. Verifies that all 074 * masters are properly shutdown. 075 */ 076 @Test 077 public void testMasterShutdown() throws Exception { 078 // Create config to use for this cluster 079 Configuration conf = HBaseConfiguration.create(); 080 081 // Start the cluster 082 try { 083 htu = new HBaseTestingUtil(conf); 084 StartTestingClusterOption option = StartTestingClusterOption.builder().numMasters(3) 085 .numRegionServers(1).numDataNodes(1).build(); 086 final SingleProcessHBaseCluster cluster = htu.startMiniCluster(option); 087 088 // wait for all master thread to spawn and start their run loop. 089 final long thirtySeconds = TimeUnit.SECONDS.toMillis(30); 090 final long oneSecond = TimeUnit.SECONDS.toMillis(1); 091 assertNotEquals(-1, htu.waitFor(thirtySeconds, oneSecond, () -> { 092 final List<MasterThread> masterThreads = cluster.getMasterThreads(); 093 return masterThreads != null && masterThreads.size() >= 3 094 && masterThreads.stream().allMatch(Thread::isAlive); 095 })); 096 097 // find the active master 098 final HMaster active = cluster.getMaster(); 099 assertNotNull(active); 100 101 // make sure the other two are backup masters 102 ClusterMetrics status = active.getClusterMetrics(); 103 assertEquals(2, status.getBackupMasterNames().size()); 104 105 // tell the active master to shutdown the cluster 106 active.shutdown(); 107 assertNotEquals(-1, htu.waitFor(thirtySeconds, oneSecond, 108 () -> CollectionUtils.isEmpty(cluster.getLiveMasterThreads()))); 109 assertNotEquals(-1, htu.waitFor(thirtySeconds, oneSecond, 110 () -> CollectionUtils.isEmpty(cluster.getLiveRegionServerThreads()))); 111 } finally { 112 if (htu != null) { 113 htu.shutdownMiniCluster(); 114 htu = null; 115 } 116 } 117 } 118 119 /** 120 * This test appears to be an intentional race between a thread that issues a shutdown RPC to the 121 * master, while the master is concurrently realizing it cannot initialize because there are no 122 * region servers available to it. The expected behavior is that master initialization is 123 * interruptable via said shutdown RPC. 124 */ 125 @Test 126 public void testMasterShutdownBeforeStartingAnyRegionServer() throws Exception { 127 LocalHBaseCluster hbaseCluster = null; 128 try { 129 htu = new HBaseTestingUtil(createMasterShutdownBeforeStartingAnyRegionServerConfiguration()); 130 131 // configure a cluster with 132 final StartTestingClusterOption options = StartTestingClusterOption.builder().numDataNodes(1) 133 .numMasters(1).numRegionServers(0).masterClass(HMaster.class) 134 .rsClass(SingleProcessHBaseCluster.MiniHBaseClusterRegionServer.class).createRootDir(true) 135 .build(); 136 137 // Can't simply `htu.startMiniCluster(options)` because that method waits for the master to 138 // start completely. However, this test's premise is that a partially started master should 139 // still respond to a shutdown RPC. So instead, we manage each component lifecycle 140 // independently. 141 // I think it's not worth refactoring HTU's helper methods just for this class. 142 htu.startMiniDFSCluster(options.getNumDataNodes()); 143 htu.startMiniZKCluster(options.getNumZkServers()); 144 htu.createRootDir(); 145 hbaseCluster = new LocalHBaseCluster(htu.getConfiguration(), options.getNumMasters(), 146 options.getNumRegionServers(), options.getMasterClass(), options.getRsClass()); 147 final MasterThread masterThread = hbaseCluster.getMasters().get(0); 148 149 masterThread.start(); 150 // Switching to master registry exacerbated a race in the master bootstrap that can result 151 // in a lost shutdown command (HBASE-8422, HBASE-23836). The race is essentially because 152 // the server manager in HMaster is not initialized by the time shutdown() RPC (below) is 153 // made to the master. The suspected reason as to why it was uncommon before HBASE-18095 154 // is because the connection creation with ZK registry is so slow that by then the server 155 // manager is usually init'ed in time for the RPC to be made. For now, adding an explicit 156 // wait() in the test, waiting for the server manager to become available. 157 final long timeout = TimeUnit.MINUTES.toMillis(10); 158 assertNotEquals("timeout waiting for server manager to become available.", -1, 159 htu.waitFor(timeout, () -> masterThread.getMaster().getServerManager() != null)); 160 161 // Master has come up far enough that we can terminate it without creating a zombie. 162 try { 163 // HBASE-24327 : (Resolve Flaky connection issues) 164 // shutdown() RPC can have flaky ZK connection issues. 165 // e.g 166 // ERROR [RpcServer.priority.RWQ.Fifo.read.handler=1,queue=1,port=53033] 167 // master.HMaster(2878): ZooKeeper exception trying to set cluster as down in ZK 168 // org.apache.zookeeper.KeeperException$SystemErrorException: 169 // KeeperErrorCode = SystemError 170 // 171 // However, even when above flakes happen, shutdown call does get completed even if 172 // RPC call has failure. Hence, subsequent retries will never succeed as HMaster is 173 // already shutdown. Hence, it can fail. To resolve it, after making one shutdown() 174 // call, we are ignoring IOException. 175 htu.getConnection().getAdmin().shutdown(); 176 } catch (RetriesExhaustedException e) { 177 if (e.getCause() instanceof ConnectionClosedException) { 178 LOG.info("Connection is Closed to the cluster. The cluster is already down.", e); 179 } else { 180 throw e; 181 } 182 } 183 LOG.info("Shutdown RPC sent."); 184 masterThread.join(); 185 } finally { 186 if (hbaseCluster != null) { 187 hbaseCluster.shutdown(); 188 } 189 if (htu != null) { 190 htu.shutdownMiniCluster(); 191 htu = null; 192 } 193 } 194 } 195 196 /** 197 * Create a cluster configuration suitable for 198 * {@link #testMasterShutdownBeforeStartingAnyRegionServer()}. 199 */ 200 private static Configuration createMasterShutdownBeforeStartingAnyRegionServerConfiguration() { 201 final Configuration conf = HBaseConfiguration.create(); 202 // make sure the master will wait forever in the absence of a RS. 203 conf.setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART, 1); 204 // don't need a long write pipeline for this test. 205 conf.setInt("dfs.replication", 1); 206 // reduce client retries 207 conf.setInt("hbase.client.retries.number", 1); 208 // Recoverable ZK configs are tuned more aggressively 209 conf.setInt(ReadOnlyZKClient.RECOVERY_RETRY, 3); 210 conf.setInt(ReadOnlyZKClient.RECOVERY_RETRY_INTERVAL_MILLIS, 100); 211 return conf; 212 } 213}