View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.backup;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Objects;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.fs.FileStatus;
36  import org.apache.hadoop.fs.FileSystem;
37  import org.apache.hadoop.fs.Path;
38  import org.apache.hadoop.fs.PathFilter;
39  import org.apache.hadoop.hbase.ChoreService;
40  import org.apache.hadoop.hbase.HBaseTestingUtility;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.Stoppable;
43  import org.apache.hadoop.hbase.TableName;
44  import org.apache.hadoop.hbase.client.Admin;
45  import org.apache.hadoop.hbase.master.HMaster;
46  import org.apache.hadoop.hbase.master.cleaner.DirScanPool;
47  import org.apache.hadoop.hbase.master.cleaner.HFileCleaner;
48  import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
49  import org.apache.hadoop.hbase.regionserver.HRegion;
50  import org.apache.hadoop.hbase.regionserver.HRegionServer;
51  import org.apache.hadoop.hbase.regionserver.Region;
52  import org.apache.hadoop.hbase.testclassification.MediumTests;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.apache.hadoop.hbase.util.FSUtils;
55  import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil;
56  import org.apache.hadoop.hbase.util.HFileArchiveUtil;
57  import org.apache.hadoop.hbase.util.StoppableImplementation;
58  import org.junit.After;
59  import org.junit.AfterClass;
60  import org.junit.Assert;
61  import org.junit.BeforeClass;
62  import org.junit.Test;
63  import org.junit.experimental.categories.Category;
64  
65  /**
66   * Test that the {@link HFileArchiver} correctly removes all the parts of a region when cleaning up
67   * a region
68   */
69  @Category(MediumTests.class)
70  public class TestHFileArchiving {
71  
72    private static final Log LOG = LogFactory.getLog(TestHFileArchiving.class);
73    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
74    private static final byte[] TEST_FAM = Bytes.toBytes("fam");
75  
76    private static DirScanPool POOL;
77  
78    /**
79     * Setup the config for the cluster
80     */
81    @BeforeClass
82    public static void setupCluster() throws Exception {
83      setupConf(UTIL.getConfiguration());
84      UTIL.startMiniCluster();
85  
86      // We don't want the cleaner to remove files. The tests do that.
87      UTIL.getMiniHBaseCluster().getMaster().getHFileCleaner().cancel(true);
88  
89      POOL = new DirScanPool(UTIL.getConfiguration());
90    }
91  
92    private static void setupConf(Configuration conf) {
93      // disable the ui
94      conf.setInt("hbase.regionsever.info.port", -1);
95      // drop the memstore size so we get flushes
96      conf.setInt("hbase.hregion.memstore.flush.size", 25000);
97      // disable major compactions
98      conf.setInt(HConstants.MAJOR_COMPACTION_PERIOD, 0);
99  
100     // prevent aggressive region split
101     conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
102       ConstantSizeRegionSplitPolicy.class.getName());
103   }
104 
105   @After
106   public void tearDown() throws Exception {
107     // cleanup the archive directory
108     clearArchiveDirectory();
109   }
110 
111   @AfterClass
112   public static void cleanupTest() throws Exception {
113     UTIL.shutdownMiniCluster();
114     POOL.shutdownNow();
115   }
116 
117   @Test
118   public void testRemovesRegionDirOnArchive() throws Exception {
119     TableName TABLE_NAME =
120         TableName.valueOf("testRemovesRegionDirOnArchive");
121     UTIL.createTable(TABLE_NAME, TEST_FAM);
122 
123     final Admin admin = UTIL.getHBaseAdmin();
124 
125     // get the current store files for the region
126     List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
127     // make sure we only have 1 region serving this table
128     assertEquals(1, servingRegions.size());
129     HRegion region = servingRegions.get(0);
130 
131     // and load the table
132     UTIL.loadRegion(region, TEST_FAM);
133 
134     // shutdown the table so we can manipulate the files
135     admin.disableTable(TABLE_NAME);
136 
137     FileSystem fs = UTIL.getTestFileSystem();
138 
139     // now attempt to depose the region
140     Path rootDir = region.getRegionFileSystem().getTableDir().getParent();
141     Path regionDir = FSUtils.getRegionDirFromRootDir(rootDir, region.getRegionInfo());
142 
143     HFileArchiver.archiveRegion(UTIL.getConfiguration(), fs, region.getRegionInfo());
144 
145     // check for the existence of the archive directory and some files in it
146     Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), region);
147     assertTrue(fs.exists(archiveDir));
148 
149     // check to make sure the store directory was copied
150     // check to make sure the store directory was copied
151     FileStatus[] stores = fs.listStatus(archiveDir, new PathFilter() {
152       @Override
153       public boolean accept(Path p) {
154         if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) {
155           return false;
156         }
157         return true;
158       }
159     });
160     assertTrue(stores.length == 1);
161 
162     // make sure we archived the store files
163     FileStatus[] storeFiles = fs.listStatus(stores[0].getPath());
164     assertTrue(storeFiles.length > 0);
165 
166     // then ensure the region's directory isn't present
167     assertFalse(fs.exists(regionDir));
168 
169     UTIL.deleteTable(TABLE_NAME);
170   }
171 
172   /**
173    * Test that the region directory is removed when we archive a region without store files, but
174    * still has hidden files.
175    * @throws Exception
176    */
177   @Test
178   public void testDeleteRegionWithNoStoreFiles() throws Exception {
179     TableName TABLE_NAME =
180         TableName.valueOf("testDeleteRegionWithNoStoreFiles");
181     UTIL.createTable(TABLE_NAME, TEST_FAM);
182 
183     // get the current store files for the region
184     List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
185     // make sure we only have 1 region serving this table
186     assertEquals(1, servingRegions.size());
187     HRegion region = servingRegions.get(0);
188 
189     FileSystem fs = region.getRegionFileSystem().getFileSystem();
190 
191     // make sure there are some files in the regiondir
192     Path rootDir = FSUtils.getRootDir(fs.getConf());
193     Path regionDir = FSUtils.getRegionDirFromRootDir(rootDir, region.getRegionInfo());
194     FileStatus[] regionFiles = FSUtils.listStatus(fs, regionDir, null);
195     Assert.assertNotNull("No files in the region directory", regionFiles);
196     if (LOG.isDebugEnabled()) {
197       List<Path> files = new ArrayList<Path>();
198       for (FileStatus file : regionFiles) {
199         files.add(file.getPath());
200       }
201       LOG.debug("Current files:" + files);
202     }
203     // delete the visible folders so we just have hidden files/folders
204     final PathFilter dirFilter = new FSUtils.DirFilter(fs);
205     PathFilter nonHidden = new PathFilter() {
206       @Override
207       public boolean accept(Path file) {
208         return dirFilter.accept(file) && !file.getName().toString().startsWith(".");
209       }
210     };
211     FileStatus[] storeDirs = FSUtils.listStatus(fs, regionDir, nonHidden);
212     for (FileStatus store : storeDirs) {
213       LOG.debug("Deleting store for test");
214       fs.delete(store.getPath(), true);
215     }
216 
217     // then archive the region
218     HFileArchiver.archiveRegion(UTIL.getConfiguration(), fs, region.getRegionInfo());
219 
220     // and check to make sure the region directoy got deleted
221     assertFalse("Region directory (" + regionDir + "), still exists.", fs.exists(regionDir));
222 
223     UTIL.deleteTable(TABLE_NAME);
224   }
225 
226   @Test
227   public void testArchiveOnTableDelete() throws Exception {
228     TableName TABLE_NAME =
229         TableName.valueOf("testArchiveOnTableDelete");
230     UTIL.createTable(TABLE_NAME, TEST_FAM);
231 
232     List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
233     // make sure we only have 1 region serving this table
234     assertEquals(1, servingRegions.size());
235     Region region = servingRegions.get(0);
236 
237     // get the parent RS and monitor
238     HRegionServer hrs = UTIL.getRSForFirstRegionInTable(TABLE_NAME);
239     FileSystem fs = hrs.getFileSystem();
240 
241     // put some data on the region
242     LOG.debug("-------Loading table");
243     UTIL.loadRegion(region, TEST_FAM);
244 
245     // get the hfiles in the region
246     List<Region> regions = hrs.getOnlineRegions(TABLE_NAME);
247     assertEquals("More that 1 region for test table.", 1, regions.size());
248 
249     region = regions.get(0);
250     // wait for all the compactions to complete
251     region.waitForFlushesAndCompactions();
252 
253     // disable table to prevent new updates
254     UTIL.getHBaseAdmin().disableTable(TABLE_NAME);
255     LOG.debug("Disabled table");
256 
257     // remove all the files from the archive to get a fair comparison
258     clearArchiveDirectory();
259 
260     // then get the current store files
261     byte[][]columns = region.getTableDesc().getFamiliesKeys().toArray(new byte[0][]);
262     List<String> storeFiles = region.getStoreFileList(columns);
263 
264     // then delete the table so the hfiles get archived
265     UTIL.deleteTable(TABLE_NAME);
266     LOG.debug("Deleted table");
267 
268     assertArchiveFiles(fs, storeFiles, 30000);
269   }
270 
271   private void assertArchiveFiles(FileSystem fs, List<String> storeFiles, long timeout) throws IOException {
272     long end = System.currentTimeMillis() + timeout;
273     Path archiveDir = HFileArchiveUtil.getArchivePath(UTIL.getConfiguration());
274     List<String> archivedFiles = new ArrayList<String>();
275 
276     // We have to ensure that the DeleteTableHandler is finished. HBaseAdmin.deleteXXX() can return before all files
277     // are archived. We should fix HBASE-5487 and fix synchronous operations from admin.
278     while (System.currentTimeMillis() < end) {
279       archivedFiles = getAllFileNames(fs, archiveDir);
280       if (archivedFiles.size() >= storeFiles.size()) {
281         break;
282       }
283     }
284 
285     Collections.sort(storeFiles);
286     Collections.sort(archivedFiles);
287 
288     LOG.debug("Store files:");
289     for (int i = 0; i < storeFiles.size(); i++) {
290       LOG.debug(i + " - " + storeFiles.get(i));
291     }
292     LOG.debug("Archive files:");
293     for (int i = 0; i < archivedFiles.size(); i++) {
294       LOG.debug(i + " - " + archivedFiles.get(i));
295     }
296 
297     assertTrue("Archived files are missing some of the store files!",
298       archivedFiles.containsAll(storeFiles));
299   }
300 
301 
302   /**
303    * Test that the store files are archived when a column family is removed.
304    * @throws Exception
305    */
306   @Test
307   public void testArchiveOnTableFamilyDelete() throws Exception {
308     TableName TABLE_NAME =
309         TableName.valueOf("testArchiveOnTableFamilyDelete");
310     UTIL.createTable(TABLE_NAME, new byte[][] {TEST_FAM, Bytes.toBytes("fam2")});
311 
312     List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
313     // make sure we only have 1 region serving this table
314     assertEquals(1, servingRegions.size());
315     Region region = servingRegions.get(0);
316 
317     // get the parent RS and monitor
318     HRegionServer hrs = UTIL.getRSForFirstRegionInTable(TABLE_NAME);
319     FileSystem fs = hrs.getFileSystem();
320 
321     // put some data on the region
322     LOG.debug("-------Loading table");
323     UTIL.loadRegion(region, TEST_FAM);
324 
325     // get the hfiles in the region
326     List<Region> regions = hrs.getOnlineRegions(TABLE_NAME);
327     assertEquals("More that 1 region for test table.", 1, regions.size());
328 
329     region = regions.get(0);
330     // wait for all the compactions to complete
331     region.waitForFlushesAndCompactions();
332 
333     // disable table to prevent new updates
334     UTIL.getHBaseAdmin().disableTable(TABLE_NAME);
335     LOG.debug("Disabled table");
336 
337     // remove all the files from the archive to get a fair comparison
338     clearArchiveDirectory();
339 
340     // then get the current store files
341     byte[][]columns = region.getTableDesc().getFamiliesKeys().toArray(new byte[0][]);
342     List<String> storeFiles = region.getStoreFileList(columns);
343 
344     // then delete the table so the hfiles get archived
345     UTIL.getHBaseAdmin().deleteColumn(TABLE_NAME, TEST_FAM);
346 
347     assertArchiveFiles(fs, storeFiles, 30000);
348 
349     UTIL.deleteTable(TABLE_NAME);
350   }
351 
352   /**
353    * Test HFileArchiver.resolveAndArchive() race condition HBASE-7643
354    */
355   @Test
356   public void testCleaningRace() throws Exception {
357     final long TEST_TIME = 20 * 1000;
358     final ChoreService choreService = new ChoreService("TEST_SERVER_NAME");
359 
360     Configuration conf = UTIL.getMiniHBaseCluster().getMaster().getConfiguration();
361     Path rootDir = UTIL.getDataTestDirOnTestFS("testCleaningRace");
362     FileSystem fs = UTIL.getTestFileSystem();
363 
364     Path archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
365     Path regionDir = new Path(FSUtils.getTableDir(new Path("./"),
366         TableName.valueOf("table")), "abcdef");
367     Path familyDir = new Path(regionDir, "cf");
368 
369     Path sourceRegionDir = new Path(rootDir, regionDir);
370     fs.mkdirs(sourceRegionDir);
371 
372     Stoppable stoppable = new StoppableImplementation();
373 
374     // The cleaner should be looping without long pauses to reproduce the race condition.
375     HFileCleaner cleaner = getHFileCleaner(stoppable, conf, fs, archiveDir);
376     try {
377       choreService.scheduleChore(cleaner);
378 
379       // Keep creating/archiving new files while the cleaner is running in the other thread
380       long startTime = System.currentTimeMillis();
381       for (long fid = 0; (System.currentTimeMillis() - startTime) < TEST_TIME; ++fid) {
382         Path file = new Path(familyDir,  String.valueOf(fid));
383         Path sourceFile = new Path(rootDir, file);
384         Path archiveFile = new Path(archiveDir, file);
385 
386         fs.createNewFile(sourceFile);
387 
388         try {
389           // Try to archive the file
390           HFileArchiver.archiveRegion(fs, rootDir,
391               sourceRegionDir.getParent(), sourceRegionDir);
392 
393           // The archiver succeded, the file is no longer in the original location
394           // but it's in the archive location.
395           LOG.debug("hfile=" + fid + " should be in the archive");
396           assertTrue(fs.exists(archiveFile));
397           assertFalse(fs.exists(sourceFile));
398         } catch (IOException e) {
399           // The archiver is unable to archive the file. Probably HBASE-7643 race condition.
400           // in this case, the file should not be archived, and we should have the file
401           // in the original location.
402           LOG.debug("hfile=" + fid + " should be in the source location");
403           assertFalse(fs.exists(archiveFile));
404           assertTrue(fs.exists(sourceFile));
405 
406           // Avoid to have this file in the next run
407           fs.delete(sourceFile, false);
408         }
409       }
410     } finally {
411       stoppable.stop("test end");
412       cleaner.cancel(true);
413       choreService.shutdown();
414       fs.delete(rootDir, true);
415     }
416   }
417 
418   // Avoid passing a null master to CleanerChore, see HBASE-21175
419   private HFileCleaner getHFileCleaner(Stoppable stoppable, Configuration conf, FileSystem fs,
420     Path archiveDir) throws IOException {
421     Map<String, Object> params = new HashMap<>();
422     params.put(HMaster.MASTER, UTIL.getMiniHBaseCluster().getMaster());
423     HFileCleaner cleaner = new HFileCleaner(1, stoppable, conf, fs, archiveDir, POOL);
424     return Objects.requireNonNull(cleaner);
425   }
426 
427   private void clearArchiveDirectory() throws IOException {
428     UTIL.getTestFileSystem().delete(
429       new Path(UTIL.getDefaultRootDirPath(), HConstants.HFILE_ARCHIVE_DIRECTORY), true);
430   }
431 
432   /**
433    * Get the names of all the files below the given directory
434    * @param fs
435    * @param archiveDir
436    * @return
437    * @throws IOException
438    */
439   private List<String> getAllFileNames(final FileSystem fs, Path archiveDir) throws IOException {
440     FileStatus[] files = FSUtils.listStatus(fs, archiveDir, new PathFilter() {
441       @Override
442       public boolean accept(Path p) {
443         if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) {
444           return false;
445         }
446         return true;
447       }
448     });
449     return recurseOnFiles(fs, files, new ArrayList<String>());
450   }
451 
452   /** Recursively lookup all the file names under the file[] array **/
453   private List<String> recurseOnFiles(FileSystem fs, FileStatus[] files, List<String> fileNames)
454       throws IOException {
455     if (files == null || files.length == 0) return fileNames;
456 
457     for (FileStatus file : files) {
458       if (file.isDirectory()) {
459         recurseOnFiles(fs, FSUtils.listStatus(fs, file.getPath(), null), fileNames);
460       } else fileNames.add(file.getPath().getName());
461     }
462     return fileNames;
463   }
464 }