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.master.janitor; 019 020import static org.apache.hadoop.hbase.util.HFileArchiveTestingUtil.assertArchiveEqualToOriginal; 021import static org.junit.jupiter.api.Assertions.assertEquals; 022import static org.junit.jupiter.api.Assertions.assertFalse; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024import static org.mockito.ArgumentMatchers.any; 025import static org.mockito.Mockito.doAnswer; 026import static org.mockito.Mockito.doReturn; 027import static org.mockito.Mockito.doThrow; 028import static org.mockito.Mockito.spy; 029import static org.mockito.Mockito.when; 030 031import java.io.IOException; 032import java.util.ArrayList; 033import java.util.List; 034import java.util.Map; 035import java.util.Objects; 036import java.util.SortedMap; 037import java.util.TreeMap; 038import java.util.concurrent.CountDownLatch; 039import java.util.concurrent.TimeUnit; 040import java.util.concurrent.atomic.AtomicBoolean; 041import org.apache.hadoop.fs.FSDataOutputStream; 042import org.apache.hadoop.fs.FileStatus; 043import org.apache.hadoop.fs.FileSystem; 044import org.apache.hadoop.fs.Path; 045import org.apache.hadoop.hbase.HBaseTestingUtil; 046import org.apache.hadoop.hbase.HConstants; 047import org.apache.hadoop.hbase.MetaMockingUtil; 048import org.apache.hadoop.hbase.TableName; 049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 050import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 051import org.apache.hadoop.hbase.client.RegionInfo; 052import org.apache.hadoop.hbase.client.RegionInfoBuilder; 053import org.apache.hadoop.hbase.client.Result; 054import org.apache.hadoop.hbase.client.TableDescriptor; 055import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 056import org.apache.hadoop.hbase.io.Reference; 057import org.apache.hadoop.hbase.master.MasterFileSystem; 058import org.apache.hadoop.hbase.master.MasterServices; 059import org.apache.hadoop.hbase.master.assignment.MockMasterServices; 060import org.apache.hadoop.hbase.master.janitor.CatalogJanitor.SplitParentFirstComparator; 061import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 062import org.apache.hadoop.hbase.regionserver.ChunkCreator; 063import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 064import org.apache.hadoop.hbase.regionserver.MemStoreLAB; 065import org.apache.hadoop.hbase.regionserver.StoreContext; 066import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker; 067import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 068import org.apache.hadoop.hbase.testclassification.MasterTests; 069import org.apache.hadoop.hbase.testclassification.MediumTests; 070import org.apache.hadoop.hbase.util.Bytes; 071import org.apache.hadoop.hbase.util.CommonFSUtils; 072import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 073import org.apache.hadoop.hbase.util.HFileArchiveUtil; 074import org.junit.jupiter.api.AfterEach; 075import org.junit.jupiter.api.BeforeAll; 076import org.junit.jupiter.api.BeforeEach; 077import org.junit.jupiter.api.Tag; 078import org.junit.jupiter.api.Test; 079import org.junit.jupiter.api.TestInfo; 080import org.slf4j.Logger; 081import org.slf4j.LoggerFactory; 082 083@Tag(MasterTests.TAG) 084@Tag(MediumTests.TAG) 085public class TestCatalogJanitor { 086 087 private static final Logger LOG = LoggerFactory.getLogger(TestCatalogJanitor.class); 088 089 private static final HBaseTestingUtil HTU = new HBaseTestingUtil(); 090 091 private String currentTestMethod; 092 093 private MockMasterServices masterServices; 094 private CatalogJanitor janitor; 095 096 @BeforeAll 097 public static void beforeClass() throws Exception { 098 ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null, 099 MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT); 100 } 101 102 @BeforeEach 103 public void setup(TestInfo testInfo) throws Exception { 104 this.currentTestMethod = testInfo.getTestMethod().get().getName(); 105 setRootDirAndCleanIt(HTU, this.currentTestMethod); 106 this.masterServices = new MockMasterServices(HTU.getConfiguration()); 107 this.masterServices.start(10, null); 108 this.janitor = new CatalogJanitor(masterServices); 109 } 110 111 @AfterEach 112 public void teardown() { 113 this.janitor.shutdown(true); 114 this.masterServices.stop("DONE"); 115 } 116 117 private RegionInfo createRegionInfo(TableName tableName, byte[] startKey, byte[] endKey) { 118 return createRegionInfo(tableName, startKey, endKey, false); 119 } 120 121 private RegionInfo createRegionInfo(TableName tableName, byte[] startKey, byte[] endKey, 122 boolean split) { 123 return RegionInfoBuilder.newBuilder(tableName).setStartKey(startKey).setEndKey(endKey) 124 .setSplit(split).build(); 125 } 126 127 @Test 128 public void testCleanMerge() throws IOException { 129 TableDescriptor td = createTableDescriptorForCurrentMethod(); 130 // Create regions. 131 RegionInfo merged = 132 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee")); 133 RegionInfo parenta = 134 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc")); 135 RegionInfo parentb = 136 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee")); 137 138 List<RegionInfo> parents = new ArrayList<>(); 139 parents.add(parenta); 140 parents.add(parentb); 141 142 Path rootdir = this.masterServices.getMasterFileSystem().getRootDir(); 143 Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName()); 144 Path storedir = 145 HRegionFileSystem.getStoreHomedir(tabledir, merged, td.getColumnFamilies()[0].getName()); 146 147 Path parentaRef = 148 createMergeReferenceFile(storedir, tabledir, td.getColumnFamilies()[0], merged, parenta); 149 Path parentbRef = 150 createMergeReferenceFile(storedir, tabledir, td.getColumnFamilies()[0], merged, parentb); 151 152 // references exist, should not clean 153 assertFalse(CatalogJanitor.cleanMergeRegion(masterServices, merged, parents)); 154 155 masterServices.getMasterFileSystem().getFileSystem().delete(parentaRef, false); 156 157 // one reference still exists, should not clean 158 assertFalse(CatalogJanitor.cleanMergeRegion(masterServices, merged, parents)); 159 160 masterServices.getMasterFileSystem().getFileSystem().delete(parentbRef, false); 161 162 // all references removed, should clean 163 assertTrue(CatalogJanitor.cleanMergeRegion(masterServices, merged, parents)); 164 } 165 166 @Test 167 public void testDontCleanMergeIfFileSystemException() throws IOException { 168 TableDescriptor td = createTableDescriptorForCurrentMethod(); 169 // Create regions. 170 RegionInfo merged = 171 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee")); 172 RegionInfo parenta = 173 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc")); 174 RegionInfo parentb = 175 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee")); 176 177 List<RegionInfo> parents = new ArrayList<>(); 178 parents.add(parenta); 179 parents.add(parentb); 180 181 Path rootdir = this.masterServices.getMasterFileSystem().getRootDir(); 182 Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName()); 183 Path storedir = 184 HRegionFileSystem.getStoreHomedir(tabledir, merged, td.getColumnFamilies()[0].getName()); 185 createMergeReferenceFile(storedir, tabledir, td.getColumnFamilies()[0], merged, parenta); 186 187 MasterServices mockedMasterServices = spy(masterServices); 188 MasterFileSystem mockedMasterFileSystem = spy(masterServices.getMasterFileSystem()); 189 FileSystem mockedFileSystem = spy(masterServices.getMasterFileSystem().getFileSystem()); 190 191 when(mockedMasterServices.getMasterFileSystem()).thenReturn(mockedMasterFileSystem); 192 when(mockedMasterFileSystem.getFileSystem()).thenReturn(mockedFileSystem); 193 194 // throw on the first exists check 195 doThrow(new IOException("Some exception")).when(mockedFileSystem).exists(any()); 196 197 assertFalse(CatalogJanitor.cleanMergeRegion(mockedMasterServices, merged, parents)); 198 199 // throw on the second exists check (within HRegionfileSystem) 200 AtomicBoolean returned = new AtomicBoolean(false); 201 doAnswer(invocationOnMock -> { 202 if (!returned.get()) { 203 returned.set(true); 204 return masterServices.getMasterFileSystem().getFileSystem() 205 .exists(invocationOnMock.getArgument(0)); 206 } 207 throw new IOException("Some exception"); 208 }).when(mockedFileSystem).exists(any()); 209 210 assertFalse(CatalogJanitor.cleanMergeRegion(mockedMasterServices, merged, parents)); 211 } 212 213 private Path createMergeReferenceFile(Path storeDir, Path tableDir, 214 ColumnFamilyDescriptor columnFamilyDescriptor, RegionInfo mergedRegion, RegionInfo parentRegion) 215 throws IOException { 216 Reference ref = Reference.createTopReference(mergedRegion.getStartKey()); 217 long now = EnvironmentEdgeManager.currentTime(); 218 // Reference name has this format: StoreFile#REF_NAME_PARSER 219 Path p = new Path(storeDir, Long.toString(now) + "." + parentRegion.getEncodedName()); 220 FileSystem fs = this.masterServices.getMasterFileSystem().getFileSystem(); 221 HRegionFileSystem mergedRegionFS = 222 HRegionFileSystem.create(fs.getConf(), fs, tableDir, mergedRegion); 223 StoreContext storeContext = 224 StoreContext.getBuilder().withColumnFamilyDescriptor(columnFamilyDescriptor) 225 .withFamilyStoreDirectoryPath(storeDir).withRegionFileSystem(mergedRegionFS).build(); 226 StoreFileTracker sft = StoreFileTrackerFactory.create(fs.getConf(), false, storeContext); 227 sft.createReference(ref, p); 228 return p; 229 } 230 231 /** 232 * Test clearing a split parent. 233 */ 234 @Test 235 public void testCleanParent() throws IOException, InterruptedException { 236 TableDescriptor td = createTableDescriptorForCurrentMethod(); 237 // Create regions. 238 RegionInfo parent = 239 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee")); 240 RegionInfo splita = 241 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc")); 242 RegionInfo splitb = 243 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee")); 244 // Test that when both daughter regions are in place, that we do not remove the parent. 245 Result r = createResult(parent, splita, splitb); 246 // Add a reference under splitA directory so we don't clear out the parent. 247 Path rootdir = this.masterServices.getMasterFileSystem().getRootDir(); 248 Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName()); 249 Path parentdir = new Path(tabledir, parent.getEncodedName()); 250 Path storedir = 251 HRegionFileSystem.getStoreHomedir(tabledir, splita, td.getColumnFamilies()[0].getName()); 252 Reference ref = Reference.createTopReference(Bytes.toBytes("ccc")); 253 long now = EnvironmentEdgeManager.currentTime(); 254 // Reference name has this format: StoreFile#REF_NAME_PARSER 255 Path p = new Path(storedir, Long.toString(now) + "." + parent.getEncodedName()); 256 FileSystem fs = this.masterServices.getMasterFileSystem().getFileSystem(); 257 HRegionFileSystem regionFS = 258 HRegionFileSystem.create(this.masterServices.getConfiguration(), fs, tabledir, splita); 259 StoreContext storeContext = 260 StoreContext.getBuilder().withColumnFamilyDescriptor(td.getColumnFamilies()[0]) 261 .withFamilyStoreDirectoryPath(storedir).withRegionFileSystem(regionFS).build(); 262 StoreFileTracker sft = 263 StoreFileTrackerFactory.create(this.masterServices.getConfiguration(), false, storeContext); 264 sft.createReference(ref, p); 265 assertTrue(fs.exists(p)); 266 LOG.info("Created reference " + p); 267 // Add a parentdir for kicks so can check it gets removed by the catalogjanitor. 268 fs.mkdirs(parentdir); 269 assertFalse(CatalogJanitor.cleanParent(masterServices, parent, r)); 270 ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor()); 271 assertTrue(fs.exists(parentdir)); 272 // Remove the reference file and try again. 273 assertTrue(fs.delete(p, true)); 274 assertTrue(CatalogJanitor.cleanParent(masterServices, parent, r)); 275 // Parent cleanup is run async as a procedure. Make sure parentdir is removed. 276 ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor()); 277 assertTrue(!fs.exists(parentdir)); 278 } 279 280 /** 281 * Make sure parent gets cleaned up even if daughter is cleaned up before it. 282 */ 283 @Test 284 public void testParentCleanedEvenIfDaughterGoneFirst() throws IOException, InterruptedException { 285 parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(this.currentTestMethod, 286 Bytes.toBytes("eee")); 287 } 288 289 /** 290 * Make sure last parent with empty end key gets cleaned up even if daughter is cleaned up before 291 * it. 292 */ 293 @Test 294 public void testLastParentCleanedEvenIfDaughterGoneFirst() 295 throws IOException, InterruptedException { 296 parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(this.currentTestMethod, new byte[0]); 297 } 298 299 /** 300 * @return A TableDescriptor with a tableName of current method name and a column family that is 301 * MockMasterServices.DEFAULT_COLUMN_FAMILY_NAME) 302 */ 303 private TableDescriptor createTableDescriptorForCurrentMethod() { 304 ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder 305 .newBuilder(Bytes.toBytes(MockMasterServices.DEFAULT_COLUMN_FAMILY_NAME)).build(); 306 return TableDescriptorBuilder.newBuilder(TableName.valueOf(this.currentTestMethod)) 307 .setColumnFamily(columnFamilyDescriptor).build(); 308 } 309 310 /** 311 * Make sure parent with specified end key gets cleaned up even if daughter is cleaned up before 312 * it. 313 * @param rootDir the test case name, used as the HBase testing utility root 314 * @param lastEndKey the end key of the split parent 315 */ 316 private void parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(final String rootDir, 317 final byte[] lastEndKey) throws IOException, InterruptedException { 318 TableDescriptor td = createTableDescriptorForCurrentMethod(); 319 // Create regions: aaa->{lastEndKey}, aaa->ccc, aaa->bbb, bbb->ccc, etc. 320 RegionInfo parent = createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), lastEndKey); 321 // Sleep a second else the encoded name on these regions comes out 322 // same for all with same start key and made in same second. 323 Thread.sleep(1001); 324 325 // Daughter a 326 RegionInfo splita = 327 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc")); 328 Thread.sleep(1001); 329 // Make daughters of daughter a; splitaa and splitab. 330 RegionInfo splitaa = 331 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("bbb")); 332 RegionInfo splitab = 333 createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ccc")); 334 335 // Daughter b 336 RegionInfo splitb = createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), lastEndKey); 337 Thread.sleep(1001); 338 // Make Daughters of daughterb; splitba and splitbb. 339 RegionInfo splitba = 340 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("ddd")); 341 RegionInfo splitbb = createRegionInfo(td.getTableName(), Bytes.toBytes("ddd"), lastEndKey); 342 343 // First test that our Comparator works right up in CatalogJanitor. 344 SortedMap<RegionInfo, Result> regions = 345 new TreeMap<>(new CatalogJanitor.SplitParentFirstComparator()); 346 // Now make sure that this regions map sorts as we expect it to. 347 regions.put(parent, createResult(parent, splita, splitb)); 348 regions.put(splitb, createResult(splitb, splitba, splitbb)); 349 regions.put(splita, createResult(splita, splitaa, splitab)); 350 // Assert its properly sorted. 351 int index = 0; 352 for (Map.Entry<RegionInfo, Result> e : regions.entrySet()) { 353 if (index == 0) { 354 assertTrue(e.getKey().getEncodedName().equals(parent.getEncodedName())); 355 } else if (index == 1) { 356 assertTrue(e.getKey().getEncodedName().equals(splita.getEncodedName())); 357 } else if (index == 2) { 358 assertTrue(e.getKey().getEncodedName().equals(splitb.getEncodedName())); 359 } 360 index++; 361 } 362 363 // Now play around with the cleanParent function. Create a ref from splita up to the parent. 364 Path splitaRef = 365 createReferences(this.masterServices, td, parent, splita, Bytes.toBytes("ccc"), false); 366 // Make sure actual super parent sticks around because splita has a ref. 367 assertFalse(CatalogJanitor.cleanParent(masterServices, parent, regions.get(parent))); 368 369 // splitba, and split bb, do not have dirs in fs. That means that if 370 // we test splitb, it should get cleaned up. 371 assertTrue(CatalogJanitor.cleanParent(masterServices, splitb, regions.get(splitb))); 372 373 // Now remove ref from splita to parent... so parent can be let go and so 374 // the daughter splita can be split (can't split if still references). 375 // BUT make the timing such that the daughter gets cleaned up before we 376 // can get a chance to let go of the parent. 377 FileSystem fs = FileSystem.get(HTU.getConfiguration()); 378 assertTrue(fs.delete(splitaRef, true)); 379 // Create the refs from daughters of splita. 380 Path splitaaRef = 381 createReferences(this.masterServices, td, splita, splitaa, Bytes.toBytes("bbb"), false); 382 Path splitabRef = 383 createReferences(this.masterServices, td, splita, splitab, Bytes.toBytes("bbb"), true); 384 385 // Test splita. It should stick around because references from splitab, etc. 386 assertFalse(CatalogJanitor.cleanParent(masterServices, splita, regions.get(splita))); 387 388 // Now clean up parent daughter first. Remove references from its daughters. 389 assertTrue(fs.delete(splitaaRef, true)); 390 assertTrue(fs.delete(splitabRef, true)); 391 assertTrue(CatalogJanitor.cleanParent(masterServices, splita, regions.get(splita))); 392 393 // Super parent should get cleaned up now both splita and splitb are gone. 394 assertTrue(CatalogJanitor.cleanParent(masterServices, parent, regions.get(parent))); 395 } 396 397 /** 398 * CatalogJanitor.scan() should not clean parent regions if their own parents are still 399 * referencing them. This ensures that grandparent regions do not point to deleted parent regions. 400 */ 401 @Test 402 public void testScanDoesNotCleanRegionsWithExistingParents() throws Exception { 403 TableDescriptor td = createTableDescriptorForCurrentMethod(); 404 // Create regions: aaa->{lastEndKey}, aaa->ccc, aaa->bbb, bbb->ccc, etc. 405 406 // Parent 407 RegionInfo parent = 408 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), HConstants.EMPTY_BYTE_ARRAY, true); 409 // Sleep a second else the encoded name on these regions comes out 410 // same for all with same start key and made in same second. 411 Thread.sleep(1001); 412 413 // Daughter a 414 RegionInfo splita = 415 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"), true); 416 Thread.sleep(1001); 417 418 // Make daughters of daughter a; splitaa and splitab. 419 RegionInfo splitaa = 420 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("bbb"), false); 421 RegionInfo splitab = 422 createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ccc"), false); 423 424 // Daughter b 425 RegionInfo splitb = 426 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), HConstants.EMPTY_BYTE_ARRAY); 427 Thread.sleep(1001); 428 429 // Parent has daughters splita and splitb. Splita has daughters splitaa and splitab. 430 final Map<RegionInfo, Result> splitParents = new TreeMap<>(new SplitParentFirstComparator()); 431 splitParents.put(parent, createResult(parent, splita, splitb)); 432 // simulate that splita goes offline when it is split 433 splita = RegionInfoBuilder.newBuilder(splita).setOffline(true).build(); 434 splitParents.put(splita, createResult(splita, splitaa, splitab)); 435 436 final Map<RegionInfo, Result> mergedRegions = new TreeMap<>(); 437 CatalogJanitor spy = spy(this.janitor); 438 439 CatalogJanitorReport report = new CatalogJanitorReport(); 440 report.count = 10; 441 report.mergedRegions.putAll(mergedRegions); 442 report.splitParents.putAll(splitParents); 443 444 doReturn(report).when(spy).scanForReport(); 445 446 // Create ref from splita to parent 447 LOG.info("parent=" + parent.getShortNameToLog() + ", splita=" + splita.getShortNameToLog()); 448 Path splitaRef = 449 createReferences(this.masterServices, td, parent, splita, Bytes.toBytes("ccc"), false); 450 LOG.info("Created reference " + splitaRef); 451 452 // Parent and splita should not be removed because a reference from splita to parent. 453 int gcs = spy.scan(); 454 assertEquals(0, gcs); 455 456 // Now delete the ref 457 FileSystem fs = FileSystem.get(HTU.getConfiguration()); 458 assertTrue(fs.delete(splitaRef, true)); 459 460 // now, both parent, and splita can be deleted 461 gcs = spy.scan(); 462 assertEquals(2, gcs); 463 } 464 465 /** 466 * Test that we correctly archive all the storefiles when a region is deleted 467 */ 468 @Test 469 public void testSplitParentFirstComparator() { 470 SplitParentFirstComparator comp = new SplitParentFirstComparator(); 471 TableDescriptor td = createTableDescriptorForCurrentMethod(); 472 473 /* 474 * Region splits: rootRegion --- firstRegion --- firstRegiona | |- firstRegionb | |- lastRegion 475 * --- lastRegiona --- lastRegionaa | |- lastRegionab |- lastRegionb rootRegion : [] - [] 476 * firstRegion : [] - bbb lastRegion : bbb - [] firstRegiona : [] - aaa firstRegionb : aaa - bbb 477 * lastRegiona : bbb - ddd lastRegionb : ddd - [] 478 */ 479 480 // root region 481 RegionInfo rootRegion = createRegionInfo(td.getTableName(), HConstants.EMPTY_START_ROW, 482 HConstants.EMPTY_END_ROW, true); 483 RegionInfo firstRegion = 484 createRegionInfo(td.getTableName(), HConstants.EMPTY_START_ROW, Bytes.toBytes("bbb"), true); 485 RegionInfo lastRegion = 486 createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), HConstants.EMPTY_END_ROW, true); 487 488 assertTrue(comp.compare(rootRegion, rootRegion) == 0); 489 assertTrue(comp.compare(firstRegion, firstRegion) == 0); 490 assertTrue(comp.compare(lastRegion, lastRegion) == 0); 491 assertTrue(comp.compare(rootRegion, firstRegion) < 0); 492 assertTrue(comp.compare(rootRegion, lastRegion) < 0); 493 assertTrue(comp.compare(firstRegion, lastRegion) < 0); 494 495 // first region split into a, b 496 RegionInfo firstRegiona = 497 createRegionInfo(td.getTableName(), HConstants.EMPTY_START_ROW, Bytes.toBytes("aaa"), true); 498 RegionInfo firstRegionb = 499 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("bbb"), true); 500 // last region split into a, b 501 RegionInfo lastRegiona = 502 createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ddd"), true); 503 RegionInfo lastRegionb = 504 createRegionInfo(td.getTableName(), Bytes.toBytes("ddd"), HConstants.EMPTY_END_ROW, true); 505 506 assertTrue(comp.compare(firstRegiona, firstRegiona) == 0); 507 assertTrue(comp.compare(firstRegionb, firstRegionb) == 0); 508 assertTrue(comp.compare(rootRegion, firstRegiona) < 0); 509 assertTrue(comp.compare(rootRegion, firstRegionb) < 0); 510 assertTrue(comp.compare(firstRegion, firstRegiona) < 0); 511 assertTrue(comp.compare(firstRegion, firstRegionb) < 0); 512 assertTrue(comp.compare(firstRegiona, firstRegionb) < 0); 513 514 assertTrue(comp.compare(lastRegiona, lastRegiona) == 0); 515 assertTrue(comp.compare(lastRegionb, lastRegionb) == 0); 516 assertTrue(comp.compare(rootRegion, lastRegiona) < 0); 517 assertTrue(comp.compare(rootRegion, lastRegionb) < 0); 518 assertTrue(comp.compare(lastRegion, lastRegiona) < 0); 519 assertTrue(comp.compare(lastRegion, lastRegionb) < 0); 520 assertTrue(comp.compare(lastRegiona, lastRegionb) < 0); 521 522 assertTrue(comp.compare(firstRegiona, lastRegiona) < 0); 523 assertTrue(comp.compare(firstRegiona, lastRegionb) < 0); 524 assertTrue(comp.compare(firstRegionb, lastRegiona) < 0); 525 assertTrue(comp.compare(firstRegionb, lastRegionb) < 0); 526 527 RegionInfo lastRegionaa = 528 createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ccc"), false); 529 RegionInfo lastRegionab = 530 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("ddd"), false); 531 532 assertTrue(comp.compare(lastRegiona, lastRegionaa) < 0); 533 assertTrue(comp.compare(lastRegiona, lastRegionab) < 0); 534 assertTrue(comp.compare(lastRegionaa, lastRegionab) < 0); 535 } 536 537 @Test 538 public void testArchiveOldRegion() throws Exception { 539 // Create regions. 540 TableDescriptor td = createTableDescriptorForCurrentMethod(); 541 RegionInfo parent = 542 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee")); 543 RegionInfo splita = 544 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc")); 545 RegionInfo splitb = 546 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee")); 547 548 // Test that when both daughter regions are in place, that we do not 549 // remove the parent. 550 Result parentMetaRow = createResult(parent, splita, splitb); 551 FileSystem fs = FileSystem.get(HTU.getConfiguration()); 552 Path rootdir = this.masterServices.getMasterFileSystem().getRootDir(); 553 // have to set the root directory since we use it in HFileDisposer to figure out to get to the 554 // archive directory. Otherwise, it just seems to pick the first root directory it can find (so 555 // the single test passes, but when the full suite is run, things get borked). 556 CommonFSUtils.setRootDir(fs.getConf(), rootdir); 557 Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName()); 558 Path storedir = 559 HRegionFileSystem.getStoreHomedir(tabledir, parent, td.getColumnFamilies()[0].getName()); 560 Path storeArchive = HFileArchiveUtil.getStoreArchivePath(this.masterServices.getConfiguration(), 561 parent, tabledir, td.getColumnFamilies()[0].getName()); 562 LOG.debug("Table dir:" + tabledir); 563 LOG.debug("Store dir:" + storedir); 564 LOG.debug("Store archive dir:" + storeArchive); 565 566 // add a couple of store files that we can check for 567 FileStatus[] mockFiles = addMockStoreFiles(2, this.masterServices, storedir); 568 // get the current store files for comparison 569 FileStatus[] storeFiles = fs.listStatus(storedir); 570 int index = 0; 571 for (FileStatus file : storeFiles) { 572 LOG.debug("Have store file:" + file.getPath()); 573 assertEquals(mockFiles[index].getPath(), storeFiles[index].getPath(), 574 "Got unexpected store file"); 575 index++; 576 } 577 578 // do the cleaning of the parent 579 assertTrue(CatalogJanitor.cleanParent(masterServices, parent, parentMetaRow)); 580 Path parentDir = new Path(tabledir, parent.getEncodedName()); 581 // Cleanup procedure runs async. Wait till it done. 582 ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor()); 583 assertTrue(!fs.exists(parentDir)); 584 LOG.debug("Finished cleanup of parent region"); 585 586 // and now check to make sure that the files have actually been archived 587 FileStatus[] archivedStoreFiles = fs.listStatus(storeArchive); 588 logFiles("archived files", storeFiles); 589 logFiles("archived files", archivedStoreFiles); 590 591 assertArchiveEqualToOriginal(storeFiles, archivedStoreFiles, fs); 592 593 // cleanup 594 CommonFSUtils.delete(fs, rootdir, true); 595 } 596 597 /** 598 * @param description description of the files for logging 599 * @param storeFiles the status of the files to log 600 */ 601 private void logFiles(String description, FileStatus[] storeFiles) { 602 LOG.debug("Current " + description + ": "); 603 for (FileStatus file : storeFiles) { 604 LOG.debug(Objects.toString(file.getPath())); 605 } 606 } 607 608 /** 609 * Test that if a store file with the same name is present as those already backed up cause the 610 * already archived files to be timestamped backup 611 */ 612 @Test 613 public void testDuplicateHFileResolution() throws Exception { 614 TableDescriptor td = createTableDescriptorForCurrentMethod(); 615 616 // Create regions. 617 RegionInfo parent = 618 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee")); 619 RegionInfo splita = 620 createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc")); 621 RegionInfo splitb = 622 createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee")); 623 // Test that when both daughter regions are in place, that we do not 624 // remove the parent. 625 Result r = createResult(parent, splita, splitb); 626 FileSystem fs = FileSystem.get(HTU.getConfiguration()); 627 Path rootdir = this.masterServices.getMasterFileSystem().getRootDir(); 628 // Have to set the root directory since we use it in HFileDisposer to figure out to get to the 629 // archive directory. Otherwise, it just seems to pick the first root directory it can find (so 630 // the single test passes, but when the full suite is run, things get borked). 631 CommonFSUtils.setRootDir(fs.getConf(), rootdir); 632 Path tabledir = CommonFSUtils.getTableDir(rootdir, parent.getTable()); 633 Path storedir = 634 HRegionFileSystem.getStoreHomedir(tabledir, parent, td.getColumnFamilies()[0].getName()); 635 LOG.info("Old root:" + rootdir); 636 LOG.info("Old table:" + tabledir); 637 LOG.info("Old store:" + storedir); 638 639 Path storeArchive = HFileArchiveUtil.getStoreArchivePath(this.masterServices.getConfiguration(), 640 parent, tabledir, td.getColumnFamilies()[0].getName()); 641 LOG.info("Old archive:" + storeArchive); 642 643 // enable archiving, make sure that files get archived 644 addMockStoreFiles(2, this.masterServices, storedir); 645 // get the current store files for comparison 646 FileStatus[] storeFiles = fs.listStatus(storedir); 647 // Do the cleaning of the parent 648 assertTrue(CatalogJanitor.cleanParent(masterServices, parent, r)); 649 Path parentDir = new Path(tabledir, parent.getEncodedName()); 650 ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor()); 651 assertTrue(!fs.exists(parentDir)); 652 653 // And now check to make sure that the files have actually been archived 654 FileStatus[] archivedStoreFiles = fs.listStatus(storeArchive); 655 assertArchiveEqualToOriginal(storeFiles, archivedStoreFiles, fs); 656 657 // now add store files with the same names as before to check backup 658 // enable archiving, make sure that files get archived 659 addMockStoreFiles(2, this.masterServices, storedir); 660 661 // Do the cleaning of the parent 662 assertTrue(CatalogJanitor.cleanParent(masterServices, parent, r)); 663 // Cleanup procedure runs async. Wait till it done. 664 ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor()); 665 assertTrue(!fs.exists(parentDir)); 666 667 // and now check to make sure that the files have actually been archived 668 archivedStoreFiles = fs.listStatus(storeArchive); 669 assertArchiveEqualToOriginal(storeFiles, archivedStoreFiles, fs, true); 670 } 671 672 @Test 673 public void testAlreadyRunningStatus() throws Exception { 674 int numberOfThreads = 2; 675 List<Integer> gcValues = new ArrayList<>(); 676 Thread[] threads = new Thread[numberOfThreads]; 677 for (int i = 0; i < numberOfThreads; i++) { 678 threads[i] = new Thread(() -> { 679 try { 680 gcValues.add(janitor.scan()); 681 } catch (IOException e) { 682 throw new RuntimeException(e); 683 } 684 }); 685 } 686 for (int i = 0; i < numberOfThreads; i++) { 687 threads[i].start(); 688 } 689 for (int i = 0; i < numberOfThreads; i++) { 690 threads[i].join(); 691 } 692 assertTrue(gcValues.contains(-1), "One janitor.scan() call should have returned -1"); 693 } 694 695 @Test 696 public void testAlreadyRunningStatusDoesNotClearLock() throws Exception { 697 CatalogJanitor spy = spy(this.janitor); 698 699 CountDownLatch scanStarted = new CountDownLatch(1); 700 CountDownLatch allowScanToFinish = new CountDownLatch(1); 701 702 doAnswer(invocation -> { 703 scanStarted.countDown(); 704 assertTrue(allowScanToFinish.await(15, TimeUnit.SECONDS), 705 "Timed out waiting for the test to release the first catalog janitor scan."); 706 return new CatalogJanitorReport(); 707 }).when(spy).scanForReport(); 708 709 Thread scanThread = new Thread(() -> { 710 try { 711 spy.scan(); 712 } catch (IOException e) { 713 throw new RuntimeException(e); 714 } 715 }); 716 717 scanThread.start(); 718 try { 719 // First scan acquires the lock and remains running. 720 assertTrue(scanStarted.await(5, TimeUnit.SECONDS)); 721 LOG.info("First catalog janitor scan started and waiting to finish."); 722 723 // Second scan detects that another scan is running. 724 assertEquals(-1, spy.scan()); 725 LOG.info("Second catalog janitor scan attempt returned -1."); 726 727 // The second scan must not clear the lock. 728 // Therefore, the third scan must also report that a scan is running. 729 int result = spy.scan(); 730 LOG.info("Third catalog janitor scan attempt returned {}.", result); 731 assertEquals(-1, result); 732 } finally { 733 // Let the first scan finish. 734 LOG.info("Releasing first catalog janitor scan and waiting for it to complete."); 735 allowScanToFinish.countDown(); 736 scanThread.join(5000); 737 LOG.info("First catalog janitor scan thread alive after join: {}", scanThread.isAlive()); 738 } 739 } 740 741 private FileStatus[] addMockStoreFiles(int count, MasterServices services, Path storedir) 742 throws IOException { 743 // get the existing store files 744 FileSystem fs = services.getMasterFileSystem().getFileSystem(); 745 fs.mkdirs(storedir); 746 // create the store files in the parent 747 for (int i = 0; i < count; i++) { 748 Path storeFile = new Path(storedir, "_store" + i); 749 FSDataOutputStream dos = fs.create(storeFile, true); 750 dos.writeBytes("Some data: " + i); 751 dos.close(); 752 } 753 LOG.debug("Adding " + count + " store files to the storedir:" + storedir); 754 // make sure the mock store files are there 755 FileStatus[] storeFiles = fs.listStatus(storedir); 756 assertEquals(count, storeFiles.length, "Didn't have expected store files"); 757 return storeFiles; 758 } 759 760 private String setRootDirAndCleanIt(final HBaseTestingUtil htu, final String subdir) 761 throws IOException { 762 Path testdir = htu.getDataTestDir(subdir); 763 FileSystem fs = FileSystem.get(htu.getConfiguration()); 764 if (fs.exists(testdir)) { 765 assertTrue(fs.delete(testdir, true)); 766 } 767 CommonFSUtils.setRootDir(htu.getConfiguration(), testdir); 768 return CommonFSUtils.getRootDir(htu.getConfiguration()).toString(); 769 } 770 771 private Path createReferences(final MasterServices services, final TableDescriptor td, 772 final RegionInfo parent, final RegionInfo daughter, final byte[] midkey, final boolean top) 773 throws IOException { 774 Path rootdir = services.getMasterFileSystem().getRootDir(); 775 Path tabledir = CommonFSUtils.getTableDir(rootdir, parent.getTable()); 776 Path storedir = 777 HRegionFileSystem.getStoreHomedir(tabledir, daughter, td.getColumnFamilies()[0].getName()); 778 Reference ref = 779 top ? Reference.createTopReference(midkey) : Reference.createBottomReference(midkey); 780 long now = EnvironmentEdgeManager.currentTime(); 781 // Reference name has this format: StoreFile#REF_NAME_PARSER 782 Path p = new Path(storedir, Long.toString(now) + "." + parent.getEncodedName()); 783 FileSystem fs = services.getMasterFileSystem().getFileSystem(); 784 HRegionFileSystem regionFS = 785 HRegionFileSystem.create(services.getConfiguration(), fs, tabledir, daughter); 786 StoreContext storeContext = 787 StoreContext.getBuilder().withColumnFamilyDescriptor(td.getColumnFamilies()[0]) 788 .withFamilyStoreDirectoryPath(storedir).withRegionFileSystem(regionFS).build(); 789 StoreFileTracker sft = 790 StoreFileTrackerFactory.create(services.getConfiguration(), false, storeContext); 791 sft.createReference(ref, p); 792 return p; 793 } 794 795 private Result createResult(final RegionInfo parent, final RegionInfo a, final RegionInfo b) 796 throws IOException { 797 return MetaMockingUtil.getMetaTableRowResult(parent, null, a, b); 798 } 799}