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.backup; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.List; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.fs.FileStatus; 030import org.apache.hadoop.fs.FileSystem; 031import org.apache.hadoop.fs.Path; 032import org.apache.hadoop.fs.PathFilter; 033import org.apache.hadoop.hbase.ChoreService; 034import org.apache.hadoop.hbase.HBaseClassTestRule; 035import org.apache.hadoop.hbase.HBaseTestingUtility; 036import org.apache.hadoop.hbase.HConstants; 037import org.apache.hadoop.hbase.Stoppable; 038import org.apache.hadoop.hbase.TableName; 039import org.apache.hadoop.hbase.client.Admin; 040import org.apache.hadoop.hbase.master.cleaner.HFileCleaner; 041import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; 042import org.apache.hadoop.hbase.regionserver.HRegion; 043import org.apache.hadoop.hbase.regionserver.HRegionServer; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.testclassification.MiscTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.hadoop.hbase.util.FSUtils; 048import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil; 049import org.apache.hadoop.hbase.util.HFileArchiveUtil; 050import org.apache.hadoop.hbase.util.StoppableImplementation; 051import org.junit.After; 052import org.junit.AfterClass; 053import org.junit.Assert; 054import org.junit.BeforeClass; 055import org.junit.ClassRule; 056import org.junit.Rule; 057import org.junit.Test; 058import org.junit.experimental.categories.Category; 059import org.junit.rules.TestName; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063/** 064 * Test that the {@link HFileArchiver} correctly removes all the parts of a region when cleaning up 065 * a region 066 */ 067@Category({MediumTests.class, MiscTests.class}) 068public class TestHFileArchiving { 069 070 @ClassRule 071 public static final HBaseClassTestRule CLASS_RULE = 072 HBaseClassTestRule.forClass(TestHFileArchiving.class); 073 074 private static final Logger LOG = LoggerFactory.getLogger(TestHFileArchiving.class); 075 private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 076 private static final byte[] TEST_FAM = Bytes.toBytes("fam"); 077 078 @Rule 079 public TestName name = new TestName(); 080 081 /** 082 * Setup the config for the cluster 083 */ 084 @BeforeClass 085 public static void setupCluster() throws Exception { 086 setupConf(UTIL.getConfiguration()); 087 UTIL.startMiniCluster(); 088 089 // We don't want the cleaner to remove files. The tests do that. 090 UTIL.getMiniHBaseCluster().getMaster().getHFileCleaner().cancel(true); 091 } 092 093 private static void setupConf(Configuration conf) { 094 // disable the ui 095 conf.setInt("hbase.regionsever.info.port", -1); 096 // drop the memstore size so we get flushes 097 conf.setInt("hbase.hregion.memstore.flush.size", 25000); 098 // disable major compactions 099 conf.setInt(HConstants.MAJOR_COMPACTION_PERIOD, 0); 100 101 // prevent aggressive region split 102 conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, 103 ConstantSizeRegionSplitPolicy.class.getName()); 104 } 105 106 @After 107 public void tearDown() throws Exception { 108 // cleanup the archive directory 109 try { 110 clearArchiveDirectory(); 111 } catch (IOException e) { 112 Assert.fail("Failure to delete archive directory:" + e.getMessage()); 113 } 114 } 115 116 @AfterClass 117 public static void cleanupTest() throws Exception { 118 try { 119 UTIL.shutdownMiniCluster(); 120 } catch (Exception e) { 121 // NOOP; 122 } 123 } 124 125 @Test 126 public void testRemovesRegionDirOnArchive() throws Exception { 127 final TableName tableName = TableName.valueOf(name.getMethodName()); 128 UTIL.createTable(tableName, TEST_FAM); 129 130 final Admin admin = UTIL.getAdmin(); 131 132 // get the current store files for the region 133 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 134 // make sure we only have 1 region serving this table 135 assertEquals(1, servingRegions.size()); 136 HRegion region = servingRegions.get(0); 137 138 // and load the table 139 UTIL.loadRegion(region, TEST_FAM); 140 141 // shutdown the table so we can manipulate the files 142 admin.disableTable(tableName); 143 144 FileSystem fs = UTIL.getTestFileSystem(); 145 146 // now attempt to depose the region 147 Path rootDir = region.getRegionFileSystem().getTableDir().getParent(); 148 Path regionDir = FSUtils.getRegionDirFromRootDir(rootDir, region.getRegionInfo()); 149 150 HFileArchiver.archiveRegion(UTIL.getConfiguration(), fs, region.getRegionInfo()); 151 152 // check for the existence of the archive directory and some files in it 153 Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), region); 154 assertTrue(fs.exists(archiveDir)); 155 156 // check to make sure the store directory was copied 157 // check to make sure the store directory was copied 158 FileStatus[] stores = fs.listStatus(archiveDir, new PathFilter() { 159 @Override 160 public boolean accept(Path p) { 161 if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) { 162 return false; 163 } 164 return true; 165 } 166 }); 167 assertTrue(stores.length == 1); 168 169 // make sure we archived the store files 170 FileStatus[] storeFiles = fs.listStatus(stores[0].getPath()); 171 assertTrue(storeFiles.length > 0); 172 173 // then ensure the region's directory isn't present 174 assertFalse(fs.exists(regionDir)); 175 176 UTIL.deleteTable(tableName); 177 } 178 179 /** 180 * Test that the region directory is removed when we archive a region without store files, but 181 * still has hidden files. 182 * @throws Exception 183 */ 184 @Test 185 public void testDeleteRegionWithNoStoreFiles() throws Exception { 186 final TableName tableName = TableName.valueOf(name.getMethodName()); 187 UTIL.createTable(tableName, TEST_FAM); 188 189 // get the current store files for the region 190 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 191 // make sure we only have 1 region serving this table 192 assertEquals(1, servingRegions.size()); 193 HRegion region = servingRegions.get(0); 194 195 FileSystem fs = region.getRegionFileSystem().getFileSystem(); 196 197 // make sure there are some files in the regiondir 198 Path rootDir = FSUtils.getRootDir(fs.getConf()); 199 Path regionDir = FSUtils.getRegionDirFromRootDir(rootDir, region.getRegionInfo()); 200 FileStatus[] regionFiles = FSUtils.listStatus(fs, regionDir, null); 201 Assert.assertNotNull("No files in the region directory", regionFiles); 202 if (LOG.isDebugEnabled()) { 203 List<Path> files = new ArrayList<>(); 204 for (FileStatus file : regionFiles) { 205 files.add(file.getPath()); 206 } 207 LOG.debug("Current files:" + files); 208 } 209 // delete the visible folders so we just have hidden files/folders 210 final PathFilter dirFilter = new FSUtils.DirFilter(fs); 211 PathFilter nonHidden = new PathFilter() { 212 @Override 213 public boolean accept(Path file) { 214 return dirFilter.accept(file) && !file.getName().toString().startsWith("."); 215 } 216 }; 217 FileStatus[] storeDirs = FSUtils.listStatus(fs, regionDir, nonHidden); 218 for (FileStatus store : storeDirs) { 219 LOG.debug("Deleting store for test"); 220 fs.delete(store.getPath(), true); 221 } 222 223 // then archive the region 224 HFileArchiver.archiveRegion(UTIL.getConfiguration(), fs, region.getRegionInfo()); 225 226 // and check to make sure the region directoy got deleted 227 assertFalse("Region directory (" + regionDir + "), still exists.", fs.exists(regionDir)); 228 229 UTIL.deleteTable(tableName); 230 } 231 232 @Test 233 public void testArchiveOnTableDelete() throws Exception { 234 final TableName tableName = TableName.valueOf(name.getMethodName()); 235 UTIL.createTable(tableName, TEST_FAM); 236 237 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 238 // make sure we only have 1 region serving this table 239 assertEquals(1, servingRegions.size()); 240 HRegion region = servingRegions.get(0); 241 242 // get the parent RS and monitor 243 HRegionServer hrs = UTIL.getRSForFirstRegionInTable(tableName); 244 FileSystem fs = hrs.getFileSystem(); 245 246 // put some data on the region 247 LOG.debug("-------Loading table"); 248 UTIL.loadRegion(region, TEST_FAM); 249 250 // get the hfiles in the region 251 List<HRegion> regions = hrs.getRegions(tableName); 252 assertEquals("More that 1 region for test table.", 1, regions.size()); 253 254 region = regions.get(0); 255 // wait for all the compactions to complete 256 region.waitForFlushesAndCompactions(); 257 258 // disable table to prevent new updates 259 UTIL.getAdmin().disableTable(tableName); 260 LOG.debug("Disabled table"); 261 262 // remove all the files from the archive to get a fair comparison 263 clearArchiveDirectory(); 264 265 // then get the current store files 266 byte[][]columns = region.getTableDescriptor().getColumnFamilyNames().toArray(new byte[0][]); 267 List<String> storeFiles = region.getStoreFileList(columns); 268 269 // then delete the table so the hfiles get archived 270 UTIL.deleteTable(tableName); 271 LOG.debug("Deleted table"); 272 273 assertArchiveFiles(fs, storeFiles, 30000); 274 } 275 276 private void assertArchiveFiles(FileSystem fs, List<String> storeFiles, long timeout) throws IOException { 277 long end = System.currentTimeMillis() + timeout; 278 Path archiveDir = HFileArchiveUtil.getArchivePath(UTIL.getConfiguration()); 279 List<String> archivedFiles = new ArrayList<>(); 280 281 // We have to ensure that the DeleteTableHandler is finished. HBaseAdmin.deleteXXX() can return before all files 282 // are archived. We should fix HBASE-5487 and fix synchronous operations from admin. 283 while (System.currentTimeMillis() < end) { 284 archivedFiles = getAllFileNames(fs, archiveDir); 285 if (archivedFiles.size() >= storeFiles.size()) { 286 break; 287 } 288 } 289 290 Collections.sort(storeFiles); 291 Collections.sort(archivedFiles); 292 293 LOG.debug("Store files:"); 294 for (int i = 0; i < storeFiles.size(); i++) { 295 LOG.debug(i + " - " + storeFiles.get(i)); 296 } 297 LOG.debug("Archive files:"); 298 for (int i = 0; i < archivedFiles.size(); i++) { 299 LOG.debug(i + " - " + archivedFiles.get(i)); 300 } 301 302 assertTrue("Archived files are missing some of the store files!", 303 archivedFiles.containsAll(storeFiles)); 304 } 305 306 307 /** 308 * Test that the store files are archived when a column family is removed. 309 * @throws Exception 310 */ 311 @Test 312 public void testArchiveOnTableFamilyDelete() throws Exception { 313 final TableName tableName = TableName.valueOf(name.getMethodName()); 314 UTIL.createTable(tableName, new byte[][] {TEST_FAM, Bytes.toBytes("fam2")}); 315 316 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 317 // make sure we only have 1 region serving this table 318 assertEquals(1, servingRegions.size()); 319 HRegion region = servingRegions.get(0); 320 321 // get the parent RS and monitor 322 HRegionServer hrs = UTIL.getRSForFirstRegionInTable(tableName); 323 FileSystem fs = hrs.getFileSystem(); 324 325 // put some data on the region 326 LOG.debug("-------Loading table"); 327 UTIL.loadRegion(region, TEST_FAM); 328 329 // get the hfiles in the region 330 List<HRegion> regions = hrs.getRegions(tableName); 331 assertEquals("More that 1 region for test table.", 1, regions.size()); 332 333 region = regions.get(0); 334 // wait for all the compactions to complete 335 region.waitForFlushesAndCompactions(); 336 337 // disable table to prevent new updates 338 UTIL.getAdmin().disableTable(tableName); 339 LOG.debug("Disabled table"); 340 341 // remove all the files from the archive to get a fair comparison 342 clearArchiveDirectory(); 343 344 // then get the current store files 345 byte[][]columns = region.getTableDescriptor().getColumnFamilyNames().toArray(new byte[0][]); 346 List<String> storeFiles = region.getStoreFileList(columns); 347 348 // then delete the table so the hfiles get archived 349 UTIL.getAdmin().deleteColumnFamily(tableName, TEST_FAM); 350 351 assertArchiveFiles(fs, storeFiles, 30000); 352 353 UTIL.deleteTable(tableName); 354 } 355 356 /** 357 * Test HFileArchiver.resolveAndArchive() race condition HBASE-7643 358 */ 359 @Test 360 public void testCleaningRace() throws Exception { 361 final long TEST_TIME = 20 * 1000; 362 final ChoreService choreService = new ChoreService("TEST_SERVER_NAME"); 363 364 Configuration conf = UTIL.getMiniHBaseCluster().getMaster().getConfiguration(); 365 Path rootDir = UTIL.getDataTestDirOnTestFS("testCleaningRace"); 366 FileSystem fs = UTIL.getTestFileSystem(); 367 368 Path archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY); 369 Path regionDir = new Path(FSUtils.getTableDir(new Path("./"), 370 TableName.valueOf(name.getMethodName())), "abcdef"); 371 Path familyDir = new Path(regionDir, "cf"); 372 373 Path sourceRegionDir = new Path(rootDir, regionDir); 374 fs.mkdirs(sourceRegionDir); 375 376 Stoppable stoppable = new StoppableImplementation(); 377 378 // The cleaner should be looping without long pauses to reproduce the race condition. 379 HFileCleaner cleaner = new HFileCleaner(1, stoppable, conf, fs, archiveDir); 380 try { 381 choreService.scheduleChore(cleaner); 382 383 // Keep creating/archiving new files while the cleaner is running in the other thread 384 long startTime = System.currentTimeMillis(); 385 for (long fid = 0; (System.currentTimeMillis() - startTime) < TEST_TIME; ++fid) { 386 Path file = new Path(familyDir, String.valueOf(fid)); 387 Path sourceFile = new Path(rootDir, file); 388 Path archiveFile = new Path(archiveDir, file); 389 390 fs.createNewFile(sourceFile); 391 392 try { 393 // Try to archive the file 394 HFileArchiver.archiveRegion(fs, rootDir, 395 sourceRegionDir.getParent(), sourceRegionDir); 396 397 // The archiver succeded, the file is no longer in the original location 398 // but it's in the archive location. 399 LOG.debug("hfile=" + fid + " should be in the archive"); 400 assertTrue(fs.exists(archiveFile)); 401 assertFalse(fs.exists(sourceFile)); 402 } catch (IOException e) { 403 // The archiver is unable to archive the file. Probably HBASE-7643 race condition. 404 // in this case, the file should not be archived, and we should have the file 405 // in the original location. 406 LOG.debug("hfile=" + fid + " should be in the source location"); 407 assertFalse(fs.exists(archiveFile)); 408 assertTrue(fs.exists(sourceFile)); 409 410 // Avoid to have this file in the next run 411 fs.delete(sourceFile, false); 412 } 413 } 414 } finally { 415 stoppable.stop("test end"); 416 cleaner.cancel(true); 417 choreService.shutdown(); 418 fs.delete(rootDir, true); 419 } 420 } 421 422 private void clearArchiveDirectory() throws IOException { 423 UTIL.getTestFileSystem().delete( 424 new Path(UTIL.getDefaultRootDirPath(), HConstants.HFILE_ARCHIVE_DIRECTORY), true); 425 } 426 427 /** 428 * Get the names of all the files below the given directory 429 * @param fs 430 * @param archiveDir 431 * @return 432 * @throws IOException 433 */ 434 private List<String> getAllFileNames(final FileSystem fs, Path archiveDir) throws IOException { 435 FileStatus[] files = FSUtils.listStatus(fs, archiveDir, new PathFilter() { 436 @Override 437 public boolean accept(Path p) { 438 if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) { 439 return false; 440 } 441 return true; 442 } 443 }); 444 return recurseOnFiles(fs, files, new ArrayList<>()); 445 } 446 447 /** Recursively lookup all the file names under the file[] array **/ 448 private List<String> recurseOnFiles(FileSystem fs, FileStatus[] files, List<String> fileNames) 449 throws IOException { 450 if (files == null || files.length == 0) return fileNames; 451 452 for (FileStatus file : files) { 453 if (file.isDirectory()) { 454 recurseOnFiles(fs, FSUtils.listStatus(fs, file.getPath(), null), fileNames); 455 } else fileNames.add(file.getPath().getName()); 456 } 457 return fileNames; 458 } 459}