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