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