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.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertNotNull; 023import static org.junit.Assert.assertNotSame; 024import static org.junit.Assert.assertNull; 025import static org.junit.Assert.assertTrue; 026import static org.junit.Assert.fail; 027 028import java.io.IOException; 029import java.util.Collection; 030import java.util.List; 031import java.util.Map; 032import java.util.Optional; 033import java.util.concurrent.CountDownLatch; 034import java.util.concurrent.TimeUnit; 035import java.util.concurrent.atomic.AtomicBoolean; 036 037import org.apache.hadoop.conf.Configuration; 038import org.apache.hadoop.fs.FileSystem; 039import org.apache.hadoop.fs.Path; 040import org.apache.hadoop.hbase.Coprocessor; 041import org.apache.hadoop.hbase.CoprocessorEnvironment; 042import org.apache.hadoop.hbase.HBaseClassTestRule; 043import org.apache.hadoop.hbase.HBaseTestingUtility; 044import org.apache.hadoop.hbase.HColumnDescriptor; 045import org.apache.hadoop.hbase.HConstants; 046import org.apache.hadoop.hbase.HTableDescriptor; 047import org.apache.hadoop.hbase.MasterNotRunningException; 048import org.apache.hadoop.hbase.MetaTableAccessor; 049import org.apache.hadoop.hbase.MiniHBaseCluster; 050import org.apache.hadoop.hbase.ServerName; 051import org.apache.hadoop.hbase.TableName; 052import org.apache.hadoop.hbase.UnknownRegionException; 053import org.apache.hadoop.hbase.ZooKeeperConnectionException; 054import org.apache.hadoop.hbase.client.Admin; 055import org.apache.hadoop.hbase.client.CompactionState; 056import org.apache.hadoop.hbase.client.Consistency; 057import org.apache.hadoop.hbase.client.Delete; 058import org.apache.hadoop.hbase.client.DoNotRetryRegionException; 059import org.apache.hadoop.hbase.client.Get; 060import org.apache.hadoop.hbase.client.Mutation; 061import org.apache.hadoop.hbase.client.Put; 062import org.apache.hadoop.hbase.client.RegionInfo; 063import org.apache.hadoop.hbase.client.Result; 064import org.apache.hadoop.hbase.client.ResultScanner; 065import org.apache.hadoop.hbase.client.Scan; 066import org.apache.hadoop.hbase.client.Table; 067import org.apache.hadoop.hbase.client.TestReplicasClient.SlowMeCopro; 068import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; 069import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 070import org.apache.hadoop.hbase.coprocessor.MasterObserver; 071import org.apache.hadoop.hbase.coprocessor.ObserverContext; 072import org.apache.hadoop.hbase.exceptions.UnexpectedStateException; 073import org.apache.hadoop.hbase.master.HMaster; 074import org.apache.hadoop.hbase.master.LoadBalancer; 075import org.apache.hadoop.hbase.master.MasterRpcServices; 076import org.apache.hadoop.hbase.master.NoSuchProcedureException; 077import org.apache.hadoop.hbase.master.RegionState; 078import org.apache.hadoop.hbase.master.RegionState.State; 079import org.apache.hadoop.hbase.master.assignment.AssignmentManager; 080import org.apache.hadoop.hbase.master.assignment.RegionStates; 081import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 082import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext; 083import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController; 084import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 085import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode; 086import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.ReportRegionStateTransitionRequest; 087import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.ReportRegionStateTransitionResponse; 088import org.apache.hadoop.hbase.testclassification.LargeTests; 089import org.apache.hadoop.hbase.testclassification.RegionServerTests; 090import org.apache.hadoop.hbase.util.Bytes; 091import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 092import org.apache.hadoop.hbase.util.FSUtils; 093import org.apache.hadoop.hbase.util.HBaseFsck; 094import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread; 095import org.apache.hadoop.hbase.util.RetryCounter; 096import org.apache.hadoop.hbase.util.Threads; 097import org.apache.hbase.thirdparty.com.google.protobuf.RpcController; 098import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException; 099import org.apache.zookeeper.KeeperException; 100import org.apache.zookeeper.KeeperException.NodeExistsException; 101import org.junit.After; 102import org.junit.AfterClass; 103import org.junit.Assert; 104import org.junit.Before; 105import org.junit.BeforeClass; 106import org.junit.ClassRule; 107import org.junit.Rule; 108import org.junit.Test; 109import org.junit.experimental.categories.Category; 110import org.junit.rules.TestName; 111import org.slf4j.Logger; 112import org.slf4j.LoggerFactory; 113 114/** 115 * The below tests are testing split region against a running cluster 116 */ 117@Category({RegionServerTests.class, LargeTests.class}) 118@SuppressWarnings("deprecation") 119public class TestSplitTransactionOnCluster { 120 121 @ClassRule 122 public static final HBaseClassTestRule CLASS_RULE = 123 HBaseClassTestRule.forClass(TestSplitTransactionOnCluster.class); 124 125 private static final Logger LOG = LoggerFactory.getLogger(TestSplitTransactionOnCluster.class); 126 private Admin admin = null; 127 private MiniHBaseCluster cluster = null; 128 private static final int NB_SERVERS = 3; 129 130 static final HBaseTestingUtility TESTING_UTIL = 131 new HBaseTestingUtility(); 132 133 @Rule 134 public TestName name = new TestName(); 135 136 @BeforeClass public static void before() throws Exception { 137 TESTING_UTIL.getConfiguration().setInt(HConstants.HBASE_BALANCER_PERIOD, 60000); 138 TESTING_UTIL.startMiniCluster(1, NB_SERVERS, null, MyMaster.class, null); 139 } 140 141 @AfterClass public static void after() throws Exception { 142 TESTING_UTIL.shutdownMiniCluster(); 143 } 144 145 @Before public void setup() throws IOException { 146 TESTING_UTIL.ensureSomeNonStoppedRegionServersAvailable(NB_SERVERS); 147 this.admin = TESTING_UTIL.getAdmin(); 148 this.cluster = TESTING_UTIL.getMiniHBaseCluster(); 149 } 150 151 @After 152 public void tearDown() throws Exception { 153 this.admin.close(); 154 for (HTableDescriptor htd: this.admin.listTables()) { 155 LOG.info("Tear down, remove table=" + htd.getTableName()); 156 TESTING_UTIL.deleteTable(htd.getTableName()); 157 } 158 } 159 160 private RegionInfo getAndCheckSingleTableRegion(final List<HRegion> regions) 161 throws IOException, InterruptedException { 162 assertEquals(1, regions.size()); 163 RegionInfo hri = regions.get(0).getRegionInfo(); 164 try { 165 cluster.getMaster().getAssignmentManager().waitForAssignment(hri, 600000); 166 } catch (NoSuchProcedureException e) { 167 LOG.info("Presume the procedure has been cleaned up so just proceed: " + e.toString()); 168 } 169 return hri; 170 } 171 172 private void requestSplitRegion( 173 final HRegionServer rsServer, 174 final Region region, 175 final byte[] midKey) throws IOException { 176 long procId = cluster.getMaster().splitRegion(region.getRegionInfo(), midKey, 0, 0); 177 // wait for the split to complete or get interrupted. If the split completes successfully, 178 // the procedure will return true; if the split fails, the procedure would throw exception. 179 ProcedureTestingUtility.waitProcedure(cluster.getMaster().getMasterProcedureExecutor(), procId); 180 } 181 182 @Test 183 public void testRITStateForRollback() throws Exception { 184 final TableName tableName = TableName.valueOf(name.getMethodName()); 185 final HMaster master = cluster.getMaster(); 186 try { 187 // Create table then get the single region for our new table. 188 Table t = createTableAndWait(tableName, Bytes.toBytes("cf")); 189 final List<HRegion> regions = cluster.getRegions(tableName); 190 final RegionInfo hri = getAndCheckSingleTableRegion(regions); 191 insertData(tableName, admin, t); 192 t.close(); 193 194 // Turn off balancer so it doesn't cut in and mess up our placements. 195 this.admin.setBalancerRunning(false, true); 196 // Turn off the meta scanner so it don't remove parent on us. 197 master.setCatalogJanitorEnabled(false); 198 199 // find a splittable region 200 final HRegion region = findSplittableRegion(regions); 201 assertTrue("not able to find a splittable region", region != null); 202 203 // install master co-processor to fail splits 204 master.getMasterCoprocessorHost().load( 205 FailingSplitMasterObserver.class, 206 Coprocessor.PRIORITY_USER, 207 master.getConfiguration()); 208 209 // split async 210 this.admin.splitRegion(region.getRegionInfo().getRegionName(), new byte[] {42}); 211 212 // we have to wait until the SPLITTING state is seen by the master 213 FailingSplitMasterObserver observer = 214 master.getMasterCoprocessorHost().findCoprocessor(FailingSplitMasterObserver.class); 215 assertNotNull(observer); 216 observer.latch.await(); 217 218 LOG.info("Waiting for region to come out of RIT"); 219 while (!cluster.getMaster().getAssignmentManager().getRegionStates().isRegionOnline(hri)) { 220 Threads.sleep(100); 221 } 222 assertTrue(cluster.getMaster().getAssignmentManager().getRegionStates().isRegionOnline(hri)); 223 } finally { 224 admin.setBalancerRunning(true, false); 225 master.setCatalogJanitorEnabled(true); 226 abortAndWaitForMaster(); 227 TESTING_UTIL.deleteTable(tableName); 228 } 229 } 230 231 @Test 232 public void testSplitFailedCompactionAndSplit() throws Exception { 233 final TableName tableName = TableName.valueOf(name.getMethodName()); 234 // Create table then get the single region for our new table. 235 HTableDescriptor htd = new HTableDescriptor(tableName); 236 byte[] cf = Bytes.toBytes("cf"); 237 htd.addFamily(new HColumnDescriptor(cf)); 238 admin.createTable(htd); 239 240 for (int i = 0; cluster.getRegions(tableName).isEmpty() && i < 100; i++) { 241 Thread.sleep(100); 242 } 243 assertEquals(1, cluster.getRegions(tableName).size()); 244 245 HRegion region = cluster.getRegions(tableName).get(0); 246 HStore store = region.getStore(cf); 247 int regionServerIndex = cluster.getServerWith(region.getRegionInfo().getRegionName()); 248 HRegionServer regionServer = cluster.getRegionServer(regionServerIndex); 249 250 Table t = TESTING_UTIL.getConnection().getTable(tableName); 251 // insert data 252 insertData(tableName, admin, t); 253 insertData(tableName, admin, t); 254 255 int fileNum = store.getStorefiles().size(); 256 // 0, Compaction Request 257 store.triggerMajorCompaction(); 258 Optional<CompactionContext> cc = store.requestCompaction(); 259 assertTrue(cc.isPresent()); 260 // 1, A timeout split 261 // 1.1 close region 262 assertEquals(2, region.close(false).get(cf).size()); 263 // 1.2 rollback and Region initialize again 264 region.initialize(); 265 266 // 2, Run Compaction cc 267 assertFalse(region.compact(cc.get(), store, NoLimitThroughputController.INSTANCE)); 268 assertTrue(fileNum > store.getStorefiles().size()); 269 270 // 3, Split 271 requestSplitRegion(regionServer, region, Bytes.toBytes("row3")); 272 assertEquals(2, cluster.getRegions(tableName).size()); 273 } 274 275 public static class FailingSplitMasterObserver implements MasterCoprocessor, MasterObserver { 276 volatile CountDownLatch latch; 277 278 @Override 279 public void start(CoprocessorEnvironment e) throws IOException { 280 latch = new CountDownLatch(1); 281 } 282 283 @Override 284 public Optional<MasterObserver> getMasterObserver() { 285 return Optional.of(this); 286 } 287 288 @Override 289 public void preSplitRegionBeforeMETAAction( 290 final ObserverContext<MasterCoprocessorEnvironment> ctx, 291 final byte[] splitKey, 292 final List<Mutation> metaEntries) throws IOException { 293 latch.countDown(); 294 throw new IOException("Causing rollback of region split"); 295 } 296 } 297 298 @Test 299 public void testSplitRollbackOnRegionClosing() throws IOException, InterruptedException { 300 final TableName tableName = TableName.valueOf(name.getMethodName()); 301 302 // Create table then get the single region for our new table. 303 Table t = createTableAndWait(tableName, HConstants.CATALOG_FAMILY); 304 List<HRegion> regions = cluster.getRegions(tableName); 305 RegionInfo hri = getAndCheckSingleTableRegion(regions); 306 307 int tableRegionIndex = ensureTableRegionNotOnSameServerAsMeta(admin, hri); 308 309 RegionStates regionStates = cluster.getMaster().getAssignmentManager().getRegionStates(); 310 311 // Turn off balancer so it doesn't cut in and mess up our placements. 312 this.admin.setBalancerRunning(false, true); 313 // Turn off the meta scanner so it don't remove parent on us. 314 cluster.getMaster().setCatalogJanitorEnabled(false); 315 try { 316 // Add a bit of load up into the table so splittable. 317 TESTING_UTIL.loadTable(t, HConstants.CATALOG_FAMILY, false); 318 // Get region pre-split. 319 HRegionServer server = cluster.getRegionServer(tableRegionIndex); 320 printOutRegions(server, "Initial regions: "); 321 int regionCount = cluster.getRegions(hri.getTable()).size(); 322 regionStates.updateRegionState(hri, RegionState.State.CLOSING); 323 324 // Now try splitting.... should fail. And each should successfully 325 // rollback. 326 // We don't roll back here anymore. Instead we fail-fast on construction of the 327 // split transaction. Catch the exception instead. 328 try { 329 this.admin.splitRegion(hri.getRegionName()); 330 fail(); 331 } catch (DoNotRetryRegionException e) { 332 // Expected 333 } 334 // Wait around a while and assert count of regions remains constant. 335 for (int i = 0; i < 10; i++) { 336 Thread.sleep(100); 337 assertEquals(regionCount, cluster.getRegions(hri.getTable()).size()); 338 } 339 regionStates.updateRegionState(hri, State.OPEN); 340 // Now try splitting and it should work. 341 split(hri, server, regionCount); 342 // Get daughters 343 checkAndGetDaughters(tableName); 344 // OK, so split happened after we cleared the blocking node. 345 } finally { 346 admin.setBalancerRunning(true, false); 347 cluster.getMaster().setCatalogJanitorEnabled(true); 348 t.close(); 349 } 350 } 351 352 /** 353 * Test that if daughter split on us, we won't do the shutdown handler fixup 354 * just because we can't find the immediate daughter of an offlined parent. 355 * @throws IOException 356 * @throws InterruptedException 357 */ 358 @Test 359 public void testShutdownFixupWhenDaughterHasSplit()throws IOException, InterruptedException { 360 final TableName tableName = TableName.valueOf(name.getMethodName()); 361 362 // Create table then get the single region for our new table. 363 Table t = createTableAndWait(tableName, HConstants.CATALOG_FAMILY); 364 List<HRegion> regions = cluster.getRegions(tableName); 365 RegionInfo hri = getAndCheckSingleTableRegion(regions); 366 367 int tableRegionIndex = ensureTableRegionNotOnSameServerAsMeta(admin, hri); 368 369 // Turn off balancer so it doesn't cut in and mess up our placements. 370 this.admin.setBalancerRunning(false, true); 371 // Turn off the meta scanner so it don't remove parent on us. 372 cluster.getMaster().setCatalogJanitorEnabled(false); 373 try { 374 // Add a bit of load up into the table so splittable. 375 TESTING_UTIL.loadTable(t, HConstants.CATALOG_FAMILY); 376 // Get region pre-split. 377 HRegionServer server = cluster.getRegionServer(tableRegionIndex); 378 printOutRegions(server, "Initial regions: "); 379 int regionCount = cluster.getRegions(hri.getTable()).size(); 380 // Now split. 381 split(hri, server, regionCount); 382 // Get daughters 383 List<HRegion> daughters = checkAndGetDaughters(tableName); 384 // Now split one of the daughters. 385 regionCount = cluster.getRegions(hri.getTable()).size(); 386 RegionInfo daughter = daughters.get(0).getRegionInfo(); 387 LOG.info("Daughter we are going to split: " + daughter); 388 // Compact first to ensure we have cleaned up references -- else the split 389 // will fail. 390 this.admin.compactRegion(daughter.getRegionName()); 391 RetryCounter retrier = new RetryCounter(30, 1, TimeUnit.SECONDS); 392 while (CompactionState.NONE != admin.getCompactionStateForRegion(daughter.getRegionName()) 393 && retrier.shouldRetry()) { 394 retrier.sleepUntilNextRetry(); 395 } 396 daughters = cluster.getRegions(tableName); 397 HRegion daughterRegion = null; 398 for (HRegion r : daughters) { 399 if (RegionInfo.COMPARATOR.compare(r.getRegionInfo(), daughter) == 0) { 400 daughterRegion = r; 401 // Archiving the compacted references file 402 r.getStores().get(0).closeAndArchiveCompactedFiles(); 403 LOG.info("Found matching HRI: " + daughterRegion); 404 break; 405 } 406 } 407 assertTrue(daughterRegion != null); 408 for (int i=0; i<100; i++) { 409 if (!daughterRegion.hasReferences()) break; 410 Threads.sleep(100); 411 } 412 assertFalse("Waiting for reference to be compacted", daughterRegion.hasReferences()); 413 LOG.info("Daughter hri before split (has been compacted): " + daughter); 414 split(daughter, server, regionCount); 415 // Get list of daughters 416 daughters = cluster.getRegions(tableName); 417 for (HRegion d: daughters) { 418 LOG.info("Regions before crash: " + d); 419 } 420 // Now crash the server 421 cluster.abortRegionServer(tableRegionIndex); 422 waitUntilRegionServerDead(); 423 awaitDaughters(tableName, daughters.size()); 424 // Assert daughters are online and ONLY the original daughters -- that 425 // fixup didn't insert one during server shutdown recover. 426 regions = cluster.getRegions(tableName); 427 for (HRegion d: daughters) { 428 LOG.info("Regions after crash: " + d); 429 } 430 if (daughters.size() != regions.size()) { 431 LOG.info("Daughters=" + daughters.size() + ", regions=" + regions.size()); 432 } 433 assertEquals(daughters.size(), regions.size()); 434 for (HRegion r: regions) { 435 LOG.info("Regions post crash " + r + ", contains=" + daughters.contains(r)); 436 assertTrue("Missing region post crash " + r, daughters.contains(r)); 437 } 438 } finally { 439 LOG.info("EXITING"); 440 admin.setBalancerRunning(true, false); 441 cluster.getMaster().setCatalogJanitorEnabled(true); 442 t.close(); 443 } 444 } 445 446 @Test 447 public void testSplitShouldNotThrowNPEEvenARegionHasEmptySplitFiles() throws Exception { 448 TableName userTableName = TableName.valueOf(name.getMethodName()); 449 HTableDescriptor htd = new HTableDescriptor(userTableName); 450 HColumnDescriptor hcd = new HColumnDescriptor("col"); 451 htd.addFamily(hcd); 452 admin.createTable(htd); 453 Table table = TESTING_UTIL.getConnection().getTable(userTableName); 454 try { 455 for (int i = 0; i <= 5; i++) { 456 String row = "row" + i; 457 Put p = new Put(row.getBytes()); 458 String val = "Val" + i; 459 p.addColumn("col".getBytes(), "ql".getBytes(), val.getBytes()); 460 table.put(p); 461 admin.flush(userTableName); 462 Delete d = new Delete(row.getBytes()); 463 // Do a normal delete 464 table.delete(d); 465 admin.flush(userTableName); 466 } 467 admin.majorCompact(userTableName); 468 List<RegionInfo> regionsOfTable = 469 cluster.getMaster().getAssignmentManager().getRegionStates() 470 .getRegionsOfTable(userTableName); 471 assertEquals(1, regionsOfTable.size()); 472 RegionInfo hRegionInfo = regionsOfTable.get(0); 473 Put p = new Put("row6".getBytes()); 474 p.addColumn("col".getBytes(), "ql".getBytes(), "val".getBytes()); 475 table.put(p); 476 p = new Put("row7".getBytes()); 477 p.addColumn("col".getBytes(), "ql".getBytes(), "val".getBytes()); 478 table.put(p); 479 p = new Put("row8".getBytes()); 480 p.addColumn("col".getBytes(), "ql".getBytes(), "val".getBytes()); 481 table.put(p); 482 admin.flush(userTableName); 483 admin.splitRegion(hRegionInfo.getRegionName(), "row7".getBytes()); 484 regionsOfTable = cluster.getMaster() 485 .getAssignmentManager().getRegionStates() 486 .getRegionsOfTable(userTableName); 487 488 while (regionsOfTable.size() != 2) { 489 Thread.sleep(1000); 490 regionsOfTable = cluster.getMaster() 491 .getAssignmentManager().getRegionStates() 492 .getRegionsOfTable(userTableName); 493 LOG.debug("waiting 2 regions to be available, got " + regionsOfTable.size() + 494 ": " + regionsOfTable); 495 496 } 497 Assert.assertEquals(2, regionsOfTable.size()); 498 499 Scan s = new Scan(); 500 ResultScanner scanner = table.getScanner(s); 501 int mainTableCount = 0; 502 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) { 503 mainTableCount++; 504 } 505 Assert.assertEquals(3, mainTableCount); 506 } finally { 507 table.close(); 508 } 509 } 510 511 /** 512 * Verifies HBASE-5806. Here the case is that splitting is completed but before the 513 * CJ could remove the parent region the master is killed and restarted. 514 * @throws IOException 515 * @throws InterruptedException 516 * @throws NodeExistsException 517 * @throws KeeperException 518 */ 519 @Test 520 public void testMasterRestartAtRegionSplitPendingCatalogJanitor() 521 throws IOException, InterruptedException, NodeExistsException, 522 KeeperException, ServiceException { 523 final TableName tableName = TableName.valueOf(name.getMethodName()); 524 525 // Create table then get the single region for our new table. 526 Table t = createTableAndWait(tableName, HConstants.CATALOG_FAMILY); 527 List<HRegion> regions = cluster.getRegions(tableName); 528 RegionInfo hri = getAndCheckSingleTableRegion(regions); 529 530 int tableRegionIndex = ensureTableRegionNotOnSameServerAsMeta(admin, hri); 531 532 // Turn off balancer so it doesn't cut in and mess up our placements. 533 this.admin.setBalancerRunning(false, true); 534 // Turn off the meta scanner so it don't remove parent on us. 535 cluster.getMaster().setCatalogJanitorEnabled(false); 536 try { 537 // Add a bit of load up into the table so splittable. 538 TESTING_UTIL.loadTable(t, HConstants.CATALOG_FAMILY, false); 539 // Get region pre-split. 540 HRegionServer server = cluster.getRegionServer(tableRegionIndex); 541 printOutRegions(server, "Initial regions: "); 542 // Call split. 543 this.admin.splitRegion(hri.getRegionName()); 544 List<HRegion> daughters = checkAndGetDaughters(tableName); 545 546 // Before cleanup, get a new master. 547 HMaster master = abortAndWaitForMaster(); 548 // Now call compact on the daughters and clean up any references. 549 for (HRegion daughter : daughters) { 550 daughter.compact(true); 551 RetryCounter retrier = new RetryCounter(30, 1, TimeUnit.SECONDS); 552 while (CompactionState.NONE != admin 553 .getCompactionStateForRegion(daughter.getRegionInfo().getRegionName()) 554 && retrier.shouldRetry()) { 555 retrier.sleepUntilNextRetry(); 556 } 557 daughter.getStores().get(0).closeAndArchiveCompactedFiles(); 558 assertFalse(daughter.hasReferences()); 559 } 560 // BUT calling compact on the daughters is not enough. The CatalogJanitor looks 561 // in the filesystem, and the filesystem content is not same as what the Region 562 // is reading from. Compacted-away files are picked up later by the compacted 563 // file discharger process. It runs infrequently. Make it run so CatalogJanitor 564 // doens't find any references. 565 for (RegionServerThread rst: cluster.getRegionServerThreads()) { 566 boolean oldSetting = rst.getRegionServer().compactedFileDischarger.setUseExecutor(false); 567 rst.getRegionServer().compactedFileDischarger.run(); 568 rst.getRegionServer().compactedFileDischarger.setUseExecutor(oldSetting); 569 } 570 cluster.getMaster().setCatalogJanitorEnabled(true); 571 LOG.info("Starting run of CatalogJanitor"); 572 cluster.getMaster().getCatalogJanitor().run(); 573 ProcedureTestingUtility.waitAllProcedures(cluster.getMaster().getMasterProcedureExecutor()); 574 RegionStates regionStates = master.getAssignmentManager().getRegionStates(); 575 ServerName regionServerOfRegion = regionStates.getRegionServerOfRegion(hri); 576 assertEquals(null, regionServerOfRegion); 577 } finally { 578 TESTING_UTIL.getAdmin().setBalancerRunning(true, false); 579 cluster.getMaster().setCatalogJanitorEnabled(true); 580 t.close(); 581 } 582 } 583 584 @Test 585 public void testSplitWithRegionReplicas() throws Exception { 586 final TableName tableName = TableName.valueOf(name.getMethodName()); 587 HTableDescriptor htd = TESTING_UTIL.createTableDescriptor(name.getMethodName()); 588 htd.setRegionReplication(2); 589 htd.addCoprocessor(SlowMeCopro.class.getName()); 590 // Create table then get the single region for our new table. 591 Table t = TESTING_UTIL.createTable(htd, new byte[][]{Bytes.toBytes("cf")}, null); 592 List<HRegion> oldRegions; 593 do { 594 oldRegions = cluster.getRegions(tableName); 595 Thread.sleep(10); 596 } while (oldRegions.size() != 2); 597 for (HRegion h : oldRegions) LOG.debug("OLDREGION " + h.getRegionInfo()); 598 try { 599 int regionServerIndex = cluster.getServerWith(oldRegions.get(0).getRegionInfo() 600 .getRegionName()); 601 HRegionServer regionServer = cluster.getRegionServer(regionServerIndex); 602 insertData(tableName, admin, t); 603 // Turn off balancer so it doesn't cut in and mess up our placements. 604 admin.setBalancerRunning(false, true); 605 // Turn off the meta scanner so it don't remove parent on us. 606 cluster.getMaster().setCatalogJanitorEnabled(false); 607 boolean tableExists = MetaTableAccessor.tableExists(regionServer.getConnection(), 608 tableName); 609 assertEquals("The specified table should be present.", true, tableExists); 610 final HRegion region = findSplittableRegion(oldRegions); 611 regionServerIndex = cluster.getServerWith(region.getRegionInfo().getRegionName()); 612 regionServer = cluster.getRegionServer(regionServerIndex); 613 assertTrue("not able to find a splittable region", region != null); 614 try { 615 requestSplitRegion(regionServer, region, Bytes.toBytes("row2")); 616 } catch (IOException e) { 617 e.printStackTrace(); 618 fail("Split execution should have succeeded with no exceptions thrown " + e); 619 } 620 //TESTING_UTIL.waitUntilAllRegionsAssigned(tableName); 621 List<HRegion> newRegions; 622 do { 623 newRegions = cluster.getRegions(tableName); 624 for (HRegion h : newRegions) LOG.debug("NEWREGION " + h.getRegionInfo()); 625 Thread.sleep(1000); 626 } while ((newRegions.contains(oldRegions.get(0)) || newRegions.contains(oldRegions.get(1))) 627 || newRegions.size() != 4); 628 tableExists = MetaTableAccessor.tableExists(regionServer.getConnection(), 629 tableName); 630 assertEquals("The specified table should be present.", true, tableExists); 631 // exists works on stale and we see the put after the flush 632 byte[] b1 = "row1".getBytes(); 633 Get g = new Get(b1); 634 g.setConsistency(Consistency.STRONG); 635 // The following GET will make a trip to the meta to get the new location of the 1st daughter 636 // In the process it will also get the location of the replica of the daughter (initially 637 // pointing to the parent's replica) 638 Result r = t.get(g); 639 Assert.assertFalse(r.isStale()); 640 LOG.info("exists stale after flush done"); 641 642 SlowMeCopro.getPrimaryCdl().set(new CountDownLatch(1)); 643 g = new Get(b1); 644 g.setConsistency(Consistency.TIMELINE); 645 // This will succeed because in the previous GET we get the location of the replica 646 r = t.get(g); 647 Assert.assertTrue(r.isStale()); 648 SlowMeCopro.getPrimaryCdl().get().countDown(); 649 } finally { 650 SlowMeCopro.getPrimaryCdl().get().countDown(); 651 admin.setBalancerRunning(true, false); 652 cluster.getMaster().setCatalogJanitorEnabled(true); 653 t.close(); 654 } 655 } 656 657 private void insertData(final TableName tableName, Admin admin, Table t) throws IOException, 658 InterruptedException { 659 Put p = new Put(Bytes.toBytes("row1")); 660 p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("1")); 661 t.put(p); 662 p = new Put(Bytes.toBytes("row2")); 663 p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("2")); 664 t.put(p); 665 p = new Put(Bytes.toBytes("row3")); 666 p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("3")); 667 t.put(p); 668 p = new Put(Bytes.toBytes("row4")); 669 p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("4")); 670 t.put(p); 671 admin.flush(tableName); 672 } 673 674 /** 675 * If a table has regions that have no store files in a region, they should split successfully 676 * into two regions with no store files. 677 */ 678 @Test 679 public void testSplitRegionWithNoStoreFiles() 680 throws Exception { 681 final TableName tableName = TableName.valueOf(name.getMethodName()); 682 // Create table then get the single region for our new table. 683 createTableAndWait(tableName, HConstants.CATALOG_FAMILY); 684 List<HRegion> regions = cluster.getRegions(tableName); 685 RegionInfo hri = getAndCheckSingleTableRegion(regions); 686 ensureTableRegionNotOnSameServerAsMeta(admin, hri); 687 int regionServerIndex = cluster.getServerWith(regions.get(0).getRegionInfo() 688 .getRegionName()); 689 HRegionServer regionServer = cluster.getRegionServer(regionServerIndex); 690 // Turn off balancer so it doesn't cut in and mess up our placements. 691 this.admin.setBalancerRunning(false, true); 692 // Turn off the meta scanner so it don't remove parent on us. 693 cluster.getMaster().setCatalogJanitorEnabled(false); 694 try { 695 // Precondition: we created a table with no data, no store files. 696 printOutRegions(regionServer, "Initial regions: "); 697 Configuration conf = cluster.getConfiguration(); 698 HBaseFsck.debugLsr(conf, new Path("/")); 699 Path rootDir = FSUtils.getRootDir(conf); 700 FileSystem fs = TESTING_UTIL.getDFSCluster().getFileSystem(); 701 Map<String, Path> storefiles = 702 FSUtils.getTableStoreFilePathMap(null, fs, rootDir, tableName); 703 assertEquals("Expected nothing but found " + storefiles.toString(), 0, storefiles.size()); 704 705 // find a splittable region. Refresh the regions list 706 regions = cluster.getRegions(tableName); 707 final HRegion region = findSplittableRegion(regions); 708 assertTrue("not able to find a splittable region", region != null); 709 710 // Now split. 711 try { 712 requestSplitRegion(regionServer, region, Bytes.toBytes("row2")); 713 } catch (IOException e) { 714 fail("Split execution should have succeeded with no exceptions thrown"); 715 } 716 717 // Postcondition: split the table with no store files into two regions, but still have no 718 // store files 719 List<HRegion> daughters = cluster.getRegions(tableName); 720 assertEquals(2, daughters.size()); 721 722 // check dirs 723 HBaseFsck.debugLsr(conf, new Path("/")); 724 Map<String, Path> storefilesAfter = 725 FSUtils.getTableStoreFilePathMap(null, fs, rootDir, tableName); 726 assertEquals("Expected nothing but found " + storefilesAfter.toString(), 0, 727 storefilesAfter.size()); 728 729 hri = region.getRegionInfo(); // split parent 730 AssignmentManager am = cluster.getMaster().getAssignmentManager(); 731 RegionStates regionStates = am.getRegionStates(); 732 long start = EnvironmentEdgeManager.currentTime(); 733 while (!regionStates.isRegionInState(hri, State.SPLIT)) { 734 LOG.debug("Waiting for SPLIT state on: " + hri); 735 assertFalse("Timed out in waiting split parent to be in state SPLIT", 736 EnvironmentEdgeManager.currentTime() - start > 60000); 737 Thread.sleep(500); 738 } 739 assertTrue(regionStates.isRegionInState(daughters.get(0).getRegionInfo(), State.OPEN)); 740 assertTrue(regionStates.isRegionInState(daughters.get(1).getRegionInfo(), State.OPEN)); 741 742 // We should not be able to assign it again 743 am.assign(hri); 744 assertFalse("Split region can't be assigned", 745 regionStates.isRegionInTransition(hri)); 746 assertTrue(regionStates.isRegionInState(hri, State.SPLIT)); 747 748 // We should not be able to unassign it either 749 try { 750 am.unassign(hri); 751 fail("Should have thrown exception"); 752 } catch (UnexpectedStateException e) { 753 // Expected 754 } 755 assertFalse("Split region can't be unassigned", 756 regionStates.isRegionInTransition(hri)); 757 assertTrue(regionStates.isRegionInState(hri, State.SPLIT)); 758 } finally { 759 admin.setBalancerRunning(true, false); 760 cluster.getMaster().setCatalogJanitorEnabled(true); 761 } 762 } 763 764 @Test 765 public void testStoreFileReferenceCreationWhenSplitPolicySaysToSkipRangeCheck() 766 throws Exception { 767 final TableName tableName = TableName.valueOf(name.getMethodName()); 768 try { 769 HTableDescriptor htd = new HTableDescriptor(tableName); 770 htd.addFamily(new HColumnDescriptor("f")); 771 htd.addFamily(new HColumnDescriptor("i_f")); 772 htd.setRegionSplitPolicyClassName(CustomSplitPolicy.class.getName()); 773 admin.createTable(htd); 774 List<HRegion> regions = awaitTableRegions(tableName); 775 HRegion region = regions.get(0); 776 for(int i = 3;i<9;i++) { 777 Put p = new Put(Bytes.toBytes("row"+i)); 778 p.addColumn(Bytes.toBytes("f"), Bytes.toBytes("q"), Bytes.toBytes("value" + i)); 779 p.addColumn(Bytes.toBytes("i_f"), Bytes.toBytes("q"), Bytes.toBytes("value" + i)); 780 region.put(p); 781 } 782 region.flush(true); 783 HStore store = region.getStore(Bytes.toBytes("f")); 784 Collection<HStoreFile> storefiles = store.getStorefiles(); 785 assertEquals(1, storefiles.size()); 786 assertFalse(region.hasReferences()); 787 Path referencePath = 788 region.getRegionFileSystem().splitStoreFile(region.getRegionInfo(), "f", 789 storefiles.iterator().next(), Bytes.toBytes("row1"), false, region.getSplitPolicy()); 790 assertNull(referencePath); 791 referencePath = 792 region.getRegionFileSystem().splitStoreFile(region.getRegionInfo(), "i_f", 793 storefiles.iterator().next(), Bytes.toBytes("row1"), false, region.getSplitPolicy()); 794 assertNotNull(referencePath); 795 } finally { 796 TESTING_UTIL.deleteTable(tableName); 797 } 798 } 799 800 private HRegion findSplittableRegion(final List<HRegion> regions) throws InterruptedException { 801 for (int i = 0; i < 5; ++i) { 802 for (HRegion r: regions) { 803 if (r.isSplittable() && r.getRegionInfo().getReplicaId() == 0) { 804 return(r); 805 } 806 } 807 Thread.sleep(100); 808 } 809 return(null); 810 } 811 812 private List<HRegion> checkAndGetDaughters(TableName tableName) 813 throws InterruptedException { 814 List<HRegion> daughters = null; 815 // try up to 10s 816 for (int i=0; i<100; i++) { 817 daughters = cluster.getRegions(tableName); 818 if (daughters.size() >= 2) break; 819 Thread.sleep(100); 820 } 821 assertTrue(daughters.size() >= 2); 822 return daughters; 823 } 824 825 private HMaster abortAndWaitForMaster() 826 throws IOException, InterruptedException { 827 cluster.abortMaster(0); 828 cluster.waitOnMaster(0); 829 HMaster master = cluster.startMaster().getMaster(); 830 cluster.waitForActiveAndReadyMaster(); 831 return master; 832 } 833 834 private void split(final RegionInfo hri, final HRegionServer server, final int regionCount) 835 throws IOException, InterruptedException { 836 admin.splitRegion(hri.getRegionName()); 837 for (int i = 0; cluster.getRegions(hri.getTable()).size() <= regionCount && i < 60; i++) { 838 LOG.debug("Waiting on region " + hri.getRegionNameAsString() + " to split"); 839 Thread.sleep(2000); 840 } 841 assertFalse("Waited too long for split", 842 cluster.getRegions(hri.getTable()).size() <= regionCount); 843 } 844 845 /** 846 * Ensure single table region is not on same server as the single hbase:meta table 847 * region. 848 * @param admin 849 * @param hri 850 * @return Index of the server hosting the single table region 851 * @throws UnknownRegionException 852 * @throws MasterNotRunningException 853 * @throws org.apache.hadoop.hbase.ZooKeeperConnectionException 854 * @throws InterruptedException 855 */ 856 private int ensureTableRegionNotOnSameServerAsMeta(final Admin admin, 857 final RegionInfo hri) 858 throws IOException, MasterNotRunningException, 859 ZooKeeperConnectionException, InterruptedException { 860 // Now make sure that the table region is not on same server as that hosting 861 // hbase:meta We don't want hbase:meta replay polluting our test when we later crash 862 // the table region serving server. 863 int metaServerIndex = cluster.getServerWithMeta(); 864 boolean tablesOnMaster = LoadBalancer.isTablesOnMaster(TESTING_UTIL.getConfiguration()); 865 if (tablesOnMaster) { 866 // Need to check master is supposed to host meta... perhaps it is not. 867 throw new UnsupportedOperationException(); 868 // TODO: assertTrue(metaServerIndex == -1); // meta is on master now 869 } 870 HRegionServer metaRegionServer = tablesOnMaster? 871 cluster.getMaster(): cluster.getRegionServer(metaServerIndex); 872 int tableRegionIndex = cluster.getServerWith(hri.getRegionName()); 873 assertTrue(tableRegionIndex != -1); 874 HRegionServer tableRegionServer = cluster.getRegionServer(tableRegionIndex); 875 LOG.info("MetaRegionServer=" + metaRegionServer.getServerName() + 876 ", other=" + tableRegionServer.getServerName()); 877 if (metaRegionServer.getServerName().equals(tableRegionServer.getServerName())) { 878 HRegionServer hrs = getOtherRegionServer(cluster, metaRegionServer); 879 assertNotNull(hrs); 880 assertNotNull(hri); 881 LOG.info("Moving " + hri.getRegionNameAsString() + " from " + 882 metaRegionServer.getServerName() + " to " + 883 hrs.getServerName() + "; metaServerIndex=" + metaServerIndex); 884 admin.move(hri.getEncodedNameAsBytes(), Bytes.toBytes(hrs.getServerName().toString())); 885 } 886 // Wait till table region is up on the server that is NOT carrying hbase:meta. 887 for (int i = 0; i < 100; i++) { 888 tableRegionIndex = cluster.getServerWith(hri.getRegionName()); 889 if (tableRegionIndex != -1 && tableRegionIndex != metaServerIndex) break; 890 LOG.debug("Waiting on region move off the hbase:meta server; current index " + 891 tableRegionIndex + " and metaServerIndex=" + metaServerIndex); 892 Thread.sleep(100); 893 } 894 assertTrue("Region not moved off hbase:meta server, tableRegionIndex=" + tableRegionIndex, 895 tableRegionIndex != -1 && tableRegionIndex != metaServerIndex); 896 // Verify for sure table region is not on same server as hbase:meta 897 tableRegionIndex = cluster.getServerWith(hri.getRegionName()); 898 assertTrue(tableRegionIndex != -1); 899 assertNotSame(metaServerIndex, tableRegionIndex); 900 return tableRegionIndex; 901 } 902 903 /** 904 * Find regionserver other than the one passed. 905 * Can't rely on indexes into list of regionservers since crashed servers 906 * occupy an index. 907 * @param cluster 908 * @param notThisOne 909 * @return A regionserver that is not <code>notThisOne</code> or null if none 910 * found 911 */ 912 private HRegionServer getOtherRegionServer(final MiniHBaseCluster cluster, 913 final HRegionServer notThisOne) { 914 for (RegionServerThread rst: cluster.getRegionServerThreads()) { 915 HRegionServer hrs = rst.getRegionServer(); 916 if (hrs.getServerName().equals(notThisOne.getServerName())) continue; 917 if (hrs.isStopping() || hrs.isStopped()) continue; 918 return hrs; 919 } 920 return null; 921 } 922 923 private void printOutRegions(final HRegionServer hrs, final String prefix) 924 throws IOException { 925 List<RegionInfo> regions = ProtobufUtil.getOnlineRegions(hrs.getRSRpcServices()); 926 for (RegionInfo region: regions) { 927 LOG.info(prefix + region.getRegionNameAsString()); 928 } 929 } 930 931 private void waitUntilRegionServerDead() throws InterruptedException, IOException { 932 // Wait until the master processes the RS shutdown 933 for (int i=0; (cluster.getMaster().getClusterMetrics() 934 .getLiveServerMetrics().size() > NB_SERVERS 935 || cluster.getLiveRegionServerThreads().size() > NB_SERVERS) && i<100; i++) { 936 LOG.info("Waiting on server to go down"); 937 Thread.sleep(100); 938 } 939 assertFalse("Waited too long for RS to die", 940 cluster.getMaster().getClusterMetrics(). getLiveServerMetrics().size() > NB_SERVERS 941 || cluster.getLiveRegionServerThreads().size() > NB_SERVERS); 942 } 943 944 private void awaitDaughters(TableName tableName, int numDaughters) throws InterruptedException { 945 // Wait till regions are back on line again. 946 for (int i = 0; cluster.getRegions(tableName).size() < numDaughters && i < 60; i++) { 947 LOG.info("Waiting for repair to happen"); 948 Thread.sleep(1000); 949 } 950 if (cluster.getRegions(tableName).size() < numDaughters) { 951 fail("Waiting too long for daughter regions"); 952 } 953 } 954 955 private List<HRegion> awaitTableRegions(final TableName tableName) throws InterruptedException { 956 List<HRegion> regions = null; 957 for (int i = 0; i < 100; i++) { 958 regions = cluster.getRegions(tableName); 959 if (regions.size() > 0) break; 960 Thread.sleep(100); 961 } 962 return regions; 963 } 964 965 private Table createTableAndWait(TableName tableName, byte[] cf) throws IOException, 966 InterruptedException { 967 Table t = TESTING_UTIL.createTable(tableName, cf); 968 awaitTableRegions(tableName); 969 assertTrue("Table not online: " + tableName, 970 cluster.getRegions(tableName).size() != 0); 971 return t; 972 } 973 974 // Make it public so that JVMClusterUtil can access it. 975 public static class MyMaster extends HMaster { 976 public MyMaster(Configuration conf) throws IOException, KeeperException, InterruptedException { 977 super(conf); 978 } 979 980 @Override 981 protected RSRpcServices createRpcServices() throws IOException { 982 return new MyMasterRpcServices(this); 983 } 984 } 985 986 static class MyMasterRpcServices extends MasterRpcServices { 987 static AtomicBoolean enabled = new AtomicBoolean(false); 988 989 private HMaster myMaster; 990 public MyMasterRpcServices(HMaster master) throws IOException { 991 super(master); 992 myMaster = master; 993 } 994 995 @Override 996 public ReportRegionStateTransitionResponse reportRegionStateTransition(RpcController c, 997 ReportRegionStateTransitionRequest req) throws ServiceException { 998 ReportRegionStateTransitionResponse resp = super.reportRegionStateTransition(c, req); 999 if (enabled.get() && req.getTransition(0).getTransitionCode().equals( 1000 TransitionCode.READY_TO_SPLIT) && !resp.hasErrorMessage()) { 1001 RegionStates regionStates = myMaster.getAssignmentManager().getRegionStates(); 1002 for (RegionStates.RegionStateNode regionState: 1003 regionStates.getRegionsInTransition()) { 1004 /* TODO!!!! 1005 // Find the merging_new region and remove it 1006 if (regionState.isSplittingNew()) { 1007 regionStates.deleteRegion(regionState.getRegion()); 1008 } 1009 */ 1010 } 1011 } 1012 return resp; 1013 } 1014 } 1015 1016 static class CustomSplitPolicy extends IncreasingToUpperBoundRegionSplitPolicy { 1017 1018 @Override 1019 protected boolean shouldSplit() { 1020 return true; 1021 } 1022 1023 @Override 1024 public boolean skipStoreFileRangeCheck(String familyName) { 1025 if(familyName.startsWith("i_")) { 1026 return true; 1027 } else { 1028 return false; 1029 } 1030 } 1031 } 1032} 1033