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