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