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}