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