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