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}