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