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.regionserver;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertThrows;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024import static org.junit.jupiter.api.Assertions.fail;
025
026import java.io.IOException;
027import org.apache.hadoop.hbase.HBaseTestingUtil;
028import org.apache.hadoop.hbase.HConstants;
029import org.apache.hadoop.hbase.NotServingRegionException;
030import org.apache.hadoop.hbase.ServerName;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.Put;
033import org.apache.hadoop.hbase.client.RegionInfo;
034import org.apache.hadoop.hbase.client.RegionLocator;
035import org.apache.hadoop.hbase.client.Table;
036import org.apache.hadoop.hbase.client.TableDescriptor;
037import org.apache.hadoop.hbase.master.HMaster;
038import org.apache.hadoop.hbase.regionserver.handler.OpenRegionHandler;
039import org.apache.hadoop.hbase.testclassification.MediumTests;
040import org.apache.hadoop.hbase.testclassification.RegionServerTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.apache.hadoop.hbase.util.JVMClusterUtil;
043import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
044import org.apache.hadoop.hbase.util.Threads;
045import org.junit.jupiter.api.AfterAll;
046import org.junit.jupiter.api.BeforeAll;
047import org.junit.jupiter.api.Tag;
048import org.junit.jupiter.api.Test;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
053import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
054import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos;
055import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CloseRegionRequest;
056
057/**
058 * Tests on the region server, without the master.
059 */
060@Tag(RegionServerTests.TAG)
061@Tag(MediumTests.TAG)
062public class TestRegionServerNoMaster {
063
064  private static final Logger LOG = LoggerFactory.getLogger(TestRegionServerNoMaster.class);
065  private static final int NB_SERVERS = 1;
066  private static Table table;
067  private static final byte[] row = Bytes.toBytes("ee");
068
069  private static RegionInfo hri;
070
071  private static byte[] regionName;
072  private static final HBaseTestingUtil HTU = new HBaseTestingUtil();
073
074  @BeforeAll
075  public static void before() throws Exception {
076    HTU.startMiniCluster(NB_SERVERS);
077    final TableName tableName = TableName.valueOf(TestRegionServerNoMaster.class.getSimpleName());
078
079    // Create table then get the single region for our new table.
080    table = HTU.createTable(tableName, HConstants.CATALOG_FAMILY);
081    Put p = new Put(row);
082    p.addColumn(HConstants.CATALOG_FAMILY, row, row);
083    table.put(p);
084
085    try (RegionLocator locator = HTU.getConnection().getRegionLocator(tableName)) {
086      hri = locator.getRegionLocation(row, false).getRegion();
087    }
088    regionName = hri.getRegionName();
089
090    stopMasterAndCacheMetaLocation(HTU);
091  }
092
093  public static void stopMasterAndCacheMetaLocation(HBaseTestingUtil HTU)
094    throws IOException, InterruptedException {
095    // cache meta location, so we will not go to master to lookup meta region location
096    for (JVMClusterUtil.RegionServerThread t : HTU.getMiniHBaseCluster().getRegionServerThreads()) {
097      try (RegionLocator locator =
098        t.getRegionServer().getConnection().getRegionLocator(TableName.META_TABLE_NAME)) {
099        locator.getAllRegionLocations();
100      }
101    }
102    try (RegionLocator locator = HTU.getConnection().getRegionLocator(TableName.META_TABLE_NAME)) {
103      locator.getAllRegionLocations();
104    }
105    // Stop master
106    HMaster master = HTU.getHBaseCluster().getMaster();
107    Thread masterThread = HTU.getHBaseCluster().getMasterThread();
108    master.stopMaster();
109
110    LOG.info("Waiting until master thread exits");
111    while (masterThread != null && masterThread.isAlive()) {
112      Threads.sleep(100);
113    }
114
115    HRegionServer.TEST_SKIP_REPORTING_TRANSITION = true;
116  }
117
118  /**
119   * Flush the given region in the mini cluster. Since no master, we cannot use HBaseAdmin.flush()
120   */
121  public static void flushRegion(HBaseTestingUtil HTU, RegionInfo regionInfo) throws IOException {
122    for (RegionServerThread rst : HTU.getMiniHBaseCluster().getRegionServerThreads()) {
123      HRegion region = rst.getRegionServer().getRegionByEncodedName(regionInfo.getEncodedName());
124      if (region != null) {
125        region.flush(true);
126        return;
127      }
128    }
129    throw new IOException("Region to flush cannot be found");
130  }
131
132  @AfterAll
133  public static void afterClass() throws Exception {
134    HRegionServer.TEST_SKIP_REPORTING_TRANSITION = false;
135    if (table != null) {
136      table.close();
137    }
138    HTU.shutdownMiniCluster();
139  }
140
141  private static HRegionServer getRS() {
142    return HTU.getHBaseCluster().getLiveRegionServerThreads().get(0).getRegionServer();
143  }
144
145  public static void openRegion(HBaseTestingUtil HTU, HRegionServer rs, RegionInfo hri)
146    throws Exception {
147    AdminProtos.OpenRegionRequest orr =
148      RequestConverter.buildOpenRegionRequest(rs.getServerName(), hri, null);
149    AdminProtos.OpenRegionResponse responseOpen = rs.getRpcServices().openRegion(null, orr);
150
151    assertEquals(1, responseOpen.getOpeningStateCount());
152    assertTrue(responseOpen.getOpeningState(0)
153      .equals(AdminProtos.OpenRegionResponse.RegionOpeningState.OPENED));
154
155    checkRegionIsOpened(HTU, rs, hri);
156  }
157
158  public static void checkRegionIsOpened(HBaseTestingUtil HTU, HRegionServer rs, RegionInfo hri)
159    throws Exception {
160    while (!rs.getRegionsInTransitionInRS().isEmpty()) {
161      Thread.sleep(1);
162    }
163
164    assertTrue(rs.getRegion(hri.getRegionName()).isAvailable());
165  }
166
167  public static void closeRegion(HBaseTestingUtil HTU, HRegionServer rs, RegionInfo hri)
168    throws Exception {
169    AdminProtos.CloseRegionRequest crr =
170      ProtobufUtil.buildCloseRegionRequest(rs.getServerName(), hri.getRegionName());
171    AdminProtos.CloseRegionResponse responseClose = rs.getRpcServices().closeRegion(null, crr);
172    assertTrue(responseClose.getClosed());
173    checkRegionIsClosed(HTU, rs, hri);
174  }
175
176  public static void checkRegionIsClosed(HBaseTestingUtil HTU, HRegionServer rs, RegionInfo hri)
177    throws Exception {
178    while (!rs.getRegionsInTransitionInRS().isEmpty()) {
179      Thread.sleep(1);
180    }
181
182    try {
183      assertFalse(rs.getRegion(hri.getRegionName()).isAvailable());
184    } catch (NotServingRegionException expected) {
185      // That's how it work: if the region is closed we have an exception.
186    }
187  }
188
189  /**
190   * Close the region without using ZK
191   */
192  private void closeRegionNoZK() throws Exception {
193    // no transition in ZK
194    AdminProtos.CloseRegionRequest crr =
195      ProtobufUtil.buildCloseRegionRequest(getRS().getServerName(), regionName);
196    AdminProtos.CloseRegionResponse responseClose = getRS().getRpcServices().closeRegion(null, crr);
197    assertTrue(responseClose.getClosed());
198
199    // now waiting & checking. After a while, the transition should be done and the region closed
200    checkRegionIsClosed(HTU, getRS(), hri);
201  }
202
203  @Test
204  public void testCloseByRegionServer() throws Exception {
205    closeRegionNoZK();
206    openRegion(HTU, getRS(), hri);
207  }
208
209  @Test
210  public void testMultipleCloseFromMaster() throws Exception {
211    for (int i = 0; i < 10; i++) {
212      AdminProtos.CloseRegionRequest crr =
213        ProtobufUtil.buildCloseRegionRequest(getRS().getServerName(), regionName, null);
214      try {
215        AdminProtos.CloseRegionResponse responseClose =
216          getRS().getRpcServices().closeRegion(null, crr);
217        assertTrue(responseClose.getClosed() || responseClose.hasClosed(),
218          "request " + i + " failed");
219      } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException se) {
220        assertTrue(i > 0, "The next queries may throw an exception.");
221      }
222    }
223
224    checkRegionIsClosed(HTU, getRS(), hri);
225
226    openRegion(HTU, getRS(), hri);
227  }
228
229  /**
230   * Test that if we do a close while opening it stops the opening.
231   */
232  @Test
233  public void testCancelOpeningWithoutZK() throws Exception {
234    // We close
235    closeRegionNoZK();
236    checkRegionIsClosed(HTU, getRS(), hri);
237
238    // Let do the initial steps, without having a handler
239    getRS().getRegionsInTransitionInRS().put(hri.getEncodedNameAsBytes(), Boolean.TRUE);
240
241    // That's a close without ZK.
242    AdminProtos.CloseRegionRequest crr =
243      ProtobufUtil.buildCloseRegionRequest(getRS().getServerName(), regionName);
244    assertThrows(org.apache.hbase.thirdparty.com.google.protobuf.ServiceException.class,
245      () -> getRS().getRpcServices().closeRegion(null, crr));
246
247    // The state in RIT should have changed to close
248    assertEquals(Boolean.FALSE,
249      getRS().getRegionsInTransitionInRS().get(hri.getEncodedNameAsBytes()));
250
251    // Let's start the open handler
252    TableDescriptor htd = getRS().getTableDescriptors().get(hri.getTable());
253
254    getRS().getExecutorService().submit(new OpenRegionHandler(getRS(), getRS(), hri, htd, -1));
255
256    // The open handler should have removed the region from RIT but kept the region closed
257    checkRegionIsClosed(HTU, getRS(), hri);
258
259    openRegion(HTU, getRS(), hri);
260  }
261
262  /**
263   * Tests an on-the-fly RPC that was scheduled for the earlier RS on the same port for openRegion.
264   * The region server should reject this RPC. (HBASE-9721)
265   */
266  @Test
267  public void testOpenCloseRegionRPCIntendedForPreviousServer() throws Exception {
268    assertTrue(getRS().getRegion(regionName).isAvailable());
269
270    ServerName sn = getRS().getServerName();
271    ServerName earlierServerName = ServerName.valueOf(sn.getHostname(), sn.getPort(), 1);
272
273    try {
274      CloseRegionRequest request =
275        ProtobufUtil.buildCloseRegionRequest(earlierServerName, regionName);
276      getRS().getRSRpcServices().closeRegion(null, request);
277      fail("The closeRegion should have been rejected");
278    } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException se) {
279      assertTrue(se.getCause() instanceof IOException);
280      assertTrue(
281        se.getCause().getMessage().contains("This RPC was intended for a different server"));
282    }
283
284    // actual close
285    closeRegionNoZK();
286    try {
287      AdminProtos.OpenRegionRequest orr =
288        RequestConverter.buildOpenRegionRequest(earlierServerName, hri, null);
289      getRS().getRSRpcServices().openRegion(null, orr);
290      fail("The openRegion should have been rejected");
291    } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException se) {
292      assertTrue(se.getCause() instanceof IOException);
293      assertTrue(
294        se.getCause().getMessage().contains("This RPC was intended for a different server"));
295    } finally {
296      openRegion(HTU, getRS(), hri);
297    }
298  }
299
300  @Test
301  public void testInstallShutdownHook() throws IOException {
302    // Test for HBASE-26951
303    assertTrue(HTU.getHBaseCluster().getRegionServer(0).isShutdownHookInstalled());
304  }
305}