View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.InterruptedIOException;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.UUID;
30  
31  import com.google.common.annotations.VisibleForTesting;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.fs.FSDataInputStream;
37  import org.apache.hadoop.fs.FSDataOutputStream;
38  import org.apache.hadoop.fs.FileStatus;
39  import org.apache.hadoop.fs.FileSystem;
40  import org.apache.hadoop.fs.FileUtil;
41  import org.apache.hadoop.fs.LocatedFileStatus;
42  import org.apache.hadoop.fs.Path;
43  import org.apache.hadoop.fs.permission.FsPermission;
44  import org.apache.hadoop.hbase.HColumnDescriptor;
45  import org.apache.hadoop.hbase.HConstants;
46  import org.apache.hadoop.hbase.HRegionInfo;
47  import org.apache.hadoop.hbase.HTableDescriptor;
48  import org.apache.hadoop.hbase.KeyValue;
49  import org.apache.hadoop.hbase.KeyValueUtil;
50  import org.apache.hadoop.hbase.backup.HFileArchiver;
51  import org.apache.hadoop.hbase.fs.HFileSystem;
52  import org.apache.hadoop.hbase.io.Reference;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.apache.hadoop.hbase.util.FSHDFSUtils;
55  import org.apache.hadoop.hbase.util.FSUtils;
56  import org.apache.hadoop.hbase.util.Pair;
57  import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
58  
59  import com.google.common.collect.Lists;
60  
61  /**
62   * View to an on-disk Region.
63   * Provides the set of methods necessary to interact with the on-disk region data.
64   */
65  @InterfaceAudience.Private
66  public class HRegionFileSystem {
67    private static final Log LOG = LogFactory.getLog(HRegionFileSystem.class);
68  
69    /** Name of the region info file that resides just under the region directory. */
70    public final static String REGION_INFO_FILE = ".regioninfo";
71  
72    /** Temporary subdirectory of the region directory used for merges. */
73    public static final String REGION_MERGES_DIR = ".merges";
74  
75    /** Temporary subdirectory of the region directory used for splits. */
76    public static final String REGION_SPLITS_DIR = ".splits";
77  
78    /** Temporary subdirectory of the region directory used for compaction output. */
79    @VisibleForTesting static final String REGION_TEMP_DIR = ".tmp";
80  
81    private final HRegionInfo regionInfo;
82    //regionInfo for interacting with FS (getting encodedName, etc)
83    private final HRegionInfo regionInfoForFs;
84    private final Configuration conf;
85    private final Path tableDir;
86    private final FileSystem fs;
87  
88    /**
89     * In order to handle NN connectivity hiccups, one need to retry non-idempotent operation at the
90     * client level.
91     */
92    private final int hdfsClientRetriesNumber;
93    private final int baseSleepBeforeRetries;
94    private static final int DEFAULT_HDFS_CLIENT_RETRIES_NUMBER = 10;
95    private static final int DEFAULT_BASE_SLEEP_BEFORE_RETRIES = 1000;
96  
97    /**
98     * Create a view to the on-disk region
99     * @param conf the {@link Configuration} to use
100    * @param fs {@link FileSystem} that contains the region
101    * @param tableDir {@link Path} to where the table is being stored
102    * @param regionInfo {@link HRegionInfo} for region
103    */
104   HRegionFileSystem(final Configuration conf, final FileSystem fs, final Path tableDir,
105       final HRegionInfo regionInfo) {
106     this.fs = fs;
107     this.conf = conf;
108     this.tableDir = tableDir;
109     this.regionInfo = regionInfo;
110     this.regionInfoForFs = ServerRegionReplicaUtil.getRegionInfoForFs(regionInfo);
111     this.hdfsClientRetriesNumber = conf.getInt("hdfs.client.retries.number",
112       DEFAULT_HDFS_CLIENT_RETRIES_NUMBER);
113     this.baseSleepBeforeRetries = conf.getInt("hdfs.client.sleep.before.retries",
114       DEFAULT_BASE_SLEEP_BEFORE_RETRIES);
115  }
116 
117   /** @return the underlying {@link FileSystem} */
118   public FileSystem getFileSystem() {
119     return this.fs;
120   }
121 
122   /** @return the {@link HRegionInfo} that describe this on-disk region view */
123   public HRegionInfo getRegionInfo() {
124     return this.regionInfo;
125   }
126 
127   public HRegionInfo getRegionInfoForFS() {
128     return this.regionInfoForFs;
129   }
130 
131   /** @return {@link Path} to the region's root directory. */
132   public Path getTableDir() {
133     return this.tableDir;
134   }
135 
136   /** @return {@link Path} to the region directory. */
137   public Path getRegionDir() {
138     return new Path(this.tableDir, this.regionInfoForFs.getEncodedName());
139   }
140 
141   // ===========================================================================
142   //  Temp Helpers
143   // ===========================================================================
144   /** @return {@link Path} to the region's temp directory, used for file creations */
145   Path getTempDir() {
146     return new Path(getRegionDir(), REGION_TEMP_DIR);
147   }
148 
149   /**
150    * Clean up any temp detritus that may have been left around from previous operation attempts.
151    */
152   void cleanupTempDir() throws IOException {
153     deleteDir(getTempDir());
154   }
155 
156   // ===========================================================================
157   //  Store/StoreFile Helpers
158   // ===========================================================================
159   /**
160    * Returns the directory path of the specified family
161    * @param familyName Column Family Name
162    * @return {@link Path} to the directory of the specified family
163    */
164   public Path getStoreDir(final String familyName) {
165     return new Path(this.getRegionDir(), familyName);
166   }
167 
168   /**
169    * Create the store directory for the specified family name
170    * @param familyName Column Family Name
171    * @return {@link Path} to the directory of the specified family
172    * @throws IOException if the directory creation fails.
173    */
174   Path createStoreDir(final String familyName) throws IOException {
175     Path storeDir = getStoreDir(familyName);
176     if(!fs.exists(storeDir) && !createDir(storeDir))
177       throw new IOException("Failed creating "+storeDir);
178     return storeDir;
179   }
180 
181   /**
182    * Returns the store files available for the family.
183    * This methods performs the filtering based on the valid store files.
184    * @param familyName Column Family Name
185    * @return a set of {@link StoreFileInfo} for the specified family.
186    */
187   public Collection<StoreFileInfo> getStoreFiles(final byte[] familyName) throws IOException {
188     return getStoreFiles(Bytes.toString(familyName));
189   }
190 
191   public Collection<StoreFileInfo> getStoreFiles(final String familyName) throws IOException {
192     return getStoreFiles(familyName, true);
193   }
194 
195   /**
196    * Returns the store files available for the family.
197    * This methods performs the filtering based on the valid store files.
198    * @param familyName Column Family Name
199    * @return a set of {@link StoreFileInfo} for the specified family.
200    */
201   public Collection<StoreFileInfo> getStoreFiles(final String familyName, final boolean validate)
202       throws IOException {
203     Path familyDir = getStoreDir(familyName);
204     FileStatus[] files = FSUtils.listStatus(this.fs, familyDir);
205     if (files == null) {
206       if (LOG.isTraceEnabled()) {
207         LOG.trace("No StoreFiles for: " + familyDir);
208       }
209       return null;
210     }
211 
212     ArrayList<StoreFileInfo> storeFiles = new ArrayList<StoreFileInfo>(files.length);
213     for (FileStatus status: files) {
214       if (validate && !StoreFileInfo.isValid(status)) {
215         LOG.warn("Invalid StoreFile: " + status.getPath());
216         continue;
217       }
218       StoreFileInfo info = ServerRegionReplicaUtil.getStoreFileInfo(conf, fs, regionInfo,
219         regionInfoForFs, familyName, status.getPath());
220       storeFiles.add(info);
221 
222     }
223     return storeFiles;
224   }
225 
226   /**
227    * Returns the store files' LocatedFileStatus which available for the family.
228    * This methods performs the filtering based on the valid store files.
229    * @param familyName Column Family Name
230    * @return a list of store files' LocatedFileStatus for the specified family.
231    */
232   public static List<LocatedFileStatus> getStoreFilesLocatedStatus(
233       final HRegionFileSystem regionfs, final String familyName,
234       final boolean validate) throws IOException {
235     Path familyDir = regionfs.getStoreDir(familyName);
236     List<LocatedFileStatus> locatedFileStatuses = FSUtils.listLocatedStatus(
237         regionfs.getFileSystem(), familyDir);
238     if (locatedFileStatuses == null) {
239       if (LOG.isTraceEnabled()) {
240         LOG.trace("No StoreFiles for: " + familyDir);
241       }
242       return null;
243     }
244 
245     List<LocatedFileStatus> validStoreFiles = Lists.newArrayList();
246     for (LocatedFileStatus status : locatedFileStatuses) {
247       if (validate && !StoreFileInfo.isValid(status)) {
248         LOG.warn("Invalid StoreFile: " + status.getPath());
249       } else {
250         validStoreFiles.add(status);
251       }
252     }
253     return validStoreFiles;
254   }
255 
256   /**
257    * Return Qualified Path of the specified family/file
258    *
259    * @param familyName Column Family Name
260    * @param fileName File Name
261    * @return The qualified Path for the specified family/file
262    */
263   Path getStoreFilePath(final String familyName, final String fileName) {
264     Path familyDir = getStoreDir(familyName);
265     return new Path(familyDir, fileName).makeQualified(this.fs);
266   }
267 
268   /**
269    * Return the store file information of the specified family/file.
270    *
271    * @param familyName Column Family Name
272    * @param fileName File Name
273    * @return The {@link StoreFileInfo} for the specified family/file
274    */
275   StoreFileInfo getStoreFileInfo(final String familyName, final String fileName)
276       throws IOException {
277     Path familyDir = getStoreDir(familyName);
278     return ServerRegionReplicaUtil.getStoreFileInfo(conf, fs, regionInfo,
279       regionInfoForFs, familyName, new Path(familyDir, fileName));
280   }
281 
282   /**
283    * Returns true if the specified family has reference files
284    * @param familyName Column Family Name
285    * @return true if family contains reference files
286    * @throws IOException
287    */
288   public boolean hasReferences(final String familyName) throws IOException {
289     FileStatus[] files = FSUtils.listStatus(fs, getStoreDir(familyName));
290     if (files != null) {
291       for(FileStatus stat: files) {
292         if(stat.isDirectory()) {
293           continue;
294         }
295         if(StoreFileInfo.isReference(stat.getPath())) {
296           return true;
297         }
298       }
299     }
300     return false;
301   }
302 
303   /**
304    * Check whether region has Reference file
305    * @param htd table desciptor of the region
306    * @return true if region has reference file
307    * @throws IOException
308    */
309   public boolean hasReferences(final HTableDescriptor htd) throws IOException {
310     for (HColumnDescriptor family : htd.getFamilies()) {
311       if (hasReferences(family.getNameAsString())) {
312         return true;
313       }
314     }
315     return false;
316   }
317 
318   /**
319    * @return the set of families present on disk
320    * @throws IOException
321    */
322   public Collection<String> getFamilies() throws IOException {
323     FileStatus[] fds = FSUtils.listStatus(fs, getRegionDir(), new FSUtils.FamilyDirFilter(fs));
324     if (fds == null) return null;
325 
326     ArrayList<String> families = new ArrayList<String>(fds.length);
327     for (FileStatus status: fds) {
328       families.add(status.getPath().getName());
329     }
330 
331     return families;
332   }
333 
334   /**
335    * Remove the region family from disk, archiving the store files.
336    * @param familyName Column Family Name
337    * @throws IOException if an error occours during the archiving
338    */
339   public void deleteFamily(final String familyName) throws IOException {
340     // archive family store files
341     HFileArchiver.archiveFamily(fs, conf, regionInfoForFs, tableDir, Bytes.toBytes(familyName));
342 
343     // delete the family folder
344     Path familyDir = getStoreDir(familyName);
345     if(fs.exists(familyDir) && !deleteDir(familyDir))
346       throw new IOException("Could not delete family " + familyName
347           + " from FileSystem for region " + regionInfoForFs.getRegionNameAsString() + "("
348           + regionInfoForFs.getEncodedName() + ")");
349   }
350 
351   /**
352    * Generate a unique file name, used by createTempName() and commitStoreFile()
353    * @param suffix extra information to append to the generated name
354    * @return Unique file name
355    */
356   private static String generateUniqueName(final String suffix) {
357     String name = UUID.randomUUID().toString().replaceAll("-", "");
358     if (suffix != null) name += suffix;
359     return name;
360   }
361 
362   /**
363    * Generate a unique temporary Path. Used in conjuction with commitStoreFile()
364    * to get a safer file creation.
365    * <code>
366    * Path file = fs.createTempName();
367    * ...StoreFile.Writer(file)...
368    * fs.commitStoreFile("family", file);
369    * </code>
370    *
371    * @return Unique {@link Path} of the temporary file
372    */
373   public Path createTempName() {
374     return createTempName(null);
375   }
376 
377   /**
378    * Generate a unique temporary Path. Used in conjuction with commitStoreFile()
379    * to get a safer file creation.
380    * <code>
381    * Path file = fs.createTempName();
382    * ...StoreFile.Writer(file)...
383    * fs.commitStoreFile("family", file);
384    * </code>
385    *
386    * @param suffix extra information to append to the generated name
387    * @return Unique {@link Path} of the temporary file
388    */
389   public Path createTempName(final String suffix) {
390     return new Path(getTempDir(), generateUniqueName(suffix));
391   }
392 
393   /**
394    * Move the file from a build/temp location to the main family store directory.
395    * @param familyName Family that will gain the file
396    * @param buildPath {@link Path} to the file to commit.
397    * @return The new {@link Path} of the committed file
398    * @throws IOException
399    */
400   public Path commitStoreFile(final String familyName, final Path buildPath) throws IOException {
401     Path dstPath = preCommitStoreFile(familyName, buildPath, -1, false);
402     return commitStoreFile(buildPath, dstPath);
403   }
404 
405   /**
406    * Generate the filename in the main family store directory for moving the file from a build/temp
407    *  location.
408    * @param familyName Family that will gain the file
409    * @param buildPath {@link Path} to the file to commit.
410    * @param seqNum Sequence Number to append to the file name (less then 0 if no sequence number)
411    * @param generateNewName False if you want to keep the buildPath name
412    * @return The new {@link Path} of the to be committed file
413    * @throws IOException
414    */
415   private Path preCommitStoreFile(final String familyName, final Path buildPath,
416       final long seqNum, final boolean generateNewName) throws IOException {
417     Path storeDir = getStoreDir(familyName);
418     if(!fs.exists(storeDir) && !createDir(storeDir))
419       throw new IOException("Failed creating " + storeDir);
420 
421     String name = buildPath.getName();
422     if (generateNewName) {
423       name = generateUniqueName((seqNum < 0) ? null : "_SeqId_" + seqNum + "_");
424     }
425     Path dstPath = new Path(storeDir, name);
426     if (!fs.exists(buildPath)) {
427       throw new FileNotFoundException(buildPath.toString());
428     }
429     if (LOG.isDebugEnabled()) {
430       LOG.debug("Committing store file " + buildPath + " as " + dstPath);
431     }
432     return dstPath;
433   }
434 
435   /*
436    * Moves file from staging dir to region dir
437    * @param buildPath {@link Path} to the file to commit.
438    * @param dstPath {@link Path} to the file under region dir
439    * @return The {@link Path} of the committed file
440    * @throws IOException
441    */
442   Path commitStoreFile(final Path buildPath, Path dstPath) throws IOException {
443     // buildPath exists, therefore not doing an exists() check.
444     if (!rename(buildPath, dstPath)) {
445       throw new IOException("Failed rename of " + buildPath + " to " + dstPath);
446     }
447     return dstPath;
448   }
449 
450   /**
451    * Moves multiple store files to the relative region's family store directory.
452    * @param storeFiles list of store files divided by family
453    * @throws IOException
454    */
455   void commitStoreFiles(final Map<byte[], List<StoreFile>> storeFiles) throws IOException {
456     for (Map.Entry<byte[], List<StoreFile>> es: storeFiles.entrySet()) {
457       String familyName = Bytes.toString(es.getKey());
458       for (StoreFile sf: es.getValue()) {
459         commitStoreFile(familyName, sf.getPath());
460       }
461     }
462   }
463 
464   /**
465    * Archives the specified store file from the specified family.
466    * @param familyName Family that contains the store files
467    * @param filePath {@link Path} to the store file to remove
468    * @throws IOException if the archiving fails
469    */
470   public void removeStoreFile(final String familyName, final Path filePath)
471       throws IOException {
472     HFileArchiver.archiveStoreFile(this.conf, this.fs, this.regionInfoForFs,
473         this.tableDir, Bytes.toBytes(familyName), filePath);
474   }
475 
476   /**
477    * Closes and archives the specified store files from the specified family.
478    * @param familyName Family that contains the store files
479    * @param storeFiles set of store files to remove
480    * @throws IOException if the archiving fails
481    */
482   public void removeStoreFiles(final String familyName, final Collection<StoreFile> storeFiles)
483       throws IOException {
484     HFileArchiver.archiveStoreFiles(this.conf, this.fs, this.regionInfoForFs,
485         this.tableDir, Bytes.toBytes(familyName), storeFiles);
486   }
487 
488   /**
489    * Bulk load: Add a specified store file to the specified family.
490    * If the source file is on the same different file-system is moved from the
491    * source location to the destination location, otherwise is copied over.
492    *
493    * @param familyName Family that will gain the file
494    * @param srcPath {@link Path} to the file to import
495    * @param seqNum Bulk Load sequence number
496    * @return The destination {@link Path} of the bulk loaded file
497    * @throws IOException
498    */
499   Pair<Path, Path> bulkLoadStoreFile(final String familyName, Path srcPath, long seqNum)
500       throws IOException {
501     // Copy the file if it's on another filesystem
502     FileSystem srcFs = srcPath.getFileSystem(conf);
503     srcPath = srcFs.resolvePath(srcPath);
504     FileSystem realSrcFs = srcPath.getFileSystem(conf);
505     FileSystem desFs = fs instanceof HFileSystem ? ((HFileSystem)fs).getBackingFs() : fs;
506 
507     // We can't compare FileSystem instances as equals() includes UGI instance
508     // as part of the comparison and won't work when doing SecureBulkLoad
509     // TODO deal with viewFS
510     if (!FSHDFSUtils.isSameHdfs(conf, realSrcFs, desFs)) {
511       LOG.info("Bulk-load file " + srcPath + " is on different filesystem than " +
512           "the destination store. Copying file over to destination filesystem.");
513       Path tmpPath = createTempName();
514       FileUtil.copy(realSrcFs, srcPath, fs, tmpPath, false, conf);
515       LOG.info("Copied " + srcPath + " to temporary path on destination filesystem: " + tmpPath);
516       srcPath = tmpPath;
517     }
518 
519     return new Pair<>(srcPath, preCommitStoreFile(familyName, srcPath, seqNum, true));
520   }
521 
522   // ===========================================================================
523   //  Splits Helpers
524   // ===========================================================================
525   /** @return {@link Path} to the temp directory used during split operations */
526   Path getSplitsDir() {
527     return new Path(getRegionDir(), REGION_SPLITS_DIR);
528   }
529 
530   Path getSplitsDir(final HRegionInfo hri) {
531     return new Path(getSplitsDir(), hri.getEncodedName());
532   }
533 
534   /**
535    * Clean up any split detritus that may have been left around from previous split attempts.
536    */
537   void cleanupSplitsDir() throws IOException {
538     deleteDir(getSplitsDir());
539   }
540 
541   /**
542    * Clean up any split detritus that may have been left around from previous
543    * split attempts.
544    * Call this method on initial region deploy.
545    * @throws IOException
546    */
547   void cleanupAnySplitDetritus() throws IOException {
548     Path splitdir = this.getSplitsDir();
549     if (!fs.exists(splitdir)) return;
550     // Look at the splitdir.  It could have the encoded names of the daughter
551     // regions we tried to make.  See if the daughter regions actually got made
552     // out under the tabledir.  If here under splitdir still, then the split did
553     // not complete.  Try and do cleanup.  This code WILL NOT catch the case
554     // where we successfully created daughter a but regionserver crashed during
555     // the creation of region b.  In this case, there'll be an orphan daughter
556     // dir in the filesystem.  TOOD: Fix.
557     FileStatus[] daughters = FSUtils.listStatus(fs, splitdir, new FSUtils.DirFilter(fs));
558     if (daughters != null) {
559       for (FileStatus daughter: daughters) {
560         Path daughterDir = new Path(getTableDir(), daughter.getPath().getName());
561         if (fs.exists(daughterDir) && !deleteDir(daughterDir)) {
562           throw new IOException("Failed delete of " + daughterDir);
563         }
564       }
565     }
566     cleanupSplitsDir();
567     LOG.info("Cleaned up old failed split transaction detritus: " + splitdir);
568   }
569 
570   /**
571    * Remove daughter region
572    * @param regionInfo daughter {@link HRegionInfo}
573    * @throws IOException
574    */
575   void cleanupDaughterRegion(final HRegionInfo regionInfo) throws IOException {
576     Path regionDir = new Path(this.tableDir, regionInfo.getEncodedName());
577     if (this.fs.exists(regionDir) && !deleteDir(regionDir)) {
578       throw new IOException("Failed delete of " + regionDir);
579     }
580   }
581 
582   /**
583    * Commit a daughter region, moving it from the split temporary directory
584    * to the proper location in the filesystem.
585    *
586    * @param regionInfo                 daughter {@link org.apache.hadoop.hbase.HRegionInfo}
587    * @throws IOException
588    */
589   Path commitDaughterRegion(final HRegionInfo regionInfo)
590       throws IOException {
591     Path regionDir = new Path(this.tableDir, regionInfo.getEncodedName());
592     Path daughterTmpDir = this.getSplitsDir(regionInfo);
593 
594     if (fs.exists(daughterTmpDir)) {
595 
596       // Write HRI to a file in case we need to recover hbase:meta
597       Path regionInfoFile = new Path(daughterTmpDir, REGION_INFO_FILE);
598       byte[] regionInfoContent = getRegionInfoFileContent(regionInfo);
599       writeRegionInfoFileContent(conf, fs, regionInfoFile, regionInfoContent);
600 
601       // Move the daughter temp dir to the table dir
602       if (!rename(daughterTmpDir, regionDir)) {
603         throw new IOException("Unable to rename " + daughterTmpDir + " to " + regionDir);
604       }
605     }
606 
607     return regionDir;
608   }
609 
610   /**
611    * Create the region splits directory.
612    */
613   void createSplitsDir() throws IOException {
614     Path splitdir = getSplitsDir();
615     if (fs.exists(splitdir)) {
616       LOG.info("The " + splitdir + " directory exists.  Hence deleting it to recreate it");
617       if (!deleteDir(splitdir)) {
618         throw new IOException("Failed deletion of " + splitdir
619             + " before creating them again.");
620       }
621     }
622     // splitDir doesn't exists now. No need to do an exists() call for it.
623     if (!createDir(splitdir)) {
624       throw new IOException("Failed create of " + splitdir);
625     }
626   }
627 
628   /**
629    * Write out a split reference. Package local so it doesnt leak out of
630    * regionserver.
631    * @param hri {@link HRegionInfo} of the destination
632    * @param familyName Column Family Name
633    * @param f File to split.
634    * @param splitRow Split Row
635    * @param top True if we are referring to the top half of the hfile.
636    * @return Path to created reference.
637    * @param splitPolicy
638    * @throws IOException
639    */
640   Path splitStoreFile(final HRegionInfo hri, final String familyName, final StoreFile f,
641       final byte[] splitRow, final boolean top, RegionSplitPolicy splitPolicy) throws IOException {
642 
643     if (splitPolicy == null || !splitPolicy.skipStoreFileRangeCheck(familyName)) {
644       // Check whether the split row lies in the range of the store file
645       // If it is outside the range, return directly.
646       try {
647         if (top) {
648           //check if larger than last key.
649           KeyValue splitKey = KeyValueUtil.createFirstOnRow(splitRow);
650           byte[] lastKey = f.getLastKey();
651           // If lastKey is null means storefile is empty.
652           if (lastKey == null) {
653             return null;
654           }
655           if (f.getComparator().compareFlatKey(splitKey.getBuffer(),
656             splitKey.getKeyOffset(), splitKey.getKeyLength(), lastKey, 0, lastKey.length) > 0) {
657             return null;
658           }
659         } else {
660           //check if smaller than first key
661           KeyValue splitKey = KeyValueUtil.createLastOnRow(splitRow);
662           byte[] firstKey = f.getFirstKey();
663           // If firstKey is null means storefile is empty.
664           if (firstKey == null) {
665             return null;
666           }
667           if (f.getComparator().compareFlatKey(splitKey.getBuffer(),
668             splitKey.getKeyOffset(), splitKey.getKeyLength(), firstKey, 0, firstKey.length) < 0) {
669             return null;
670           }
671         }
672       } finally {
673         f.closeReader(f.getCacheConf() != null ? f.getCacheConf().shouldEvictOnClose() : true);
674       }
675     }
676 
677     Path splitDir = new Path(getSplitsDir(hri), familyName);
678     // A reference to the bottom half of the hsf store file.
679     Reference r =
680       top ? Reference.createTopReference(splitRow): Reference.createBottomReference(splitRow);
681     // Add the referred-to regions name as a dot separated suffix.
682     // See REF_NAME_REGEX regex above.  The referred-to regions name is
683     // up in the path of the passed in <code>f</code> -- parentdir is family,
684     // then the directory above is the region name.
685     String parentRegionName = regionInfoForFs.getEncodedName();
686     // Write reference with same file id only with the other region name as
687     // suffix and into the new region location (under same family).
688     Path p = new Path(splitDir, f.getPath().getName() + "." + parentRegionName);
689     return r.write(fs, p);
690   }
691 
692   // ===========================================================================
693   //  Merge Helpers
694   // ===========================================================================
695   /** @return {@link Path} to the temp directory used during merge operations */
696   Path getMergesDir() {
697     return new Path(getRegionDir(), REGION_MERGES_DIR);
698   }
699 
700   Path getMergesDir(final HRegionInfo hri) {
701     return new Path(getMergesDir(), hri.getEncodedName());
702   }
703 
704   /**
705    * Clean up any merge detritus that may have been left around from previous merge attempts.
706    */
707   void cleanupMergesDir() throws IOException {
708     deleteDir(getMergesDir());
709   }
710 
711   /**
712    * Remove merged region
713    * @param mergedRegion {@link HRegionInfo}
714    * @throws IOException
715    */
716   void cleanupMergedRegion(final HRegionInfo mergedRegion) throws IOException {
717     Path regionDir = new Path(this.tableDir, mergedRegion.getEncodedName());
718     if (this.fs.exists(regionDir) && !this.fs.delete(regionDir, true)) {
719       throw new IOException("Failed delete of " + regionDir);
720     }
721   }
722 
723   static boolean mkdirs(FileSystem fs, Configuration conf, Path dir) throws IOException {
724     if (FSUtils.isDistributedFileSystem(fs) ||
725         !conf.getBoolean(HConstants.ENABLE_DATA_FILE_UMASK, false)) {
726       return fs.mkdirs(dir);
727     }
728     FsPermission perms = FSUtils.getFilePermissions(fs, conf, HConstants.DATA_FILE_UMASK_KEY);
729     return fs.mkdirs(dir, perms);
730   }
731   /**
732    * Create the region merges directory.
733    * @throws IOException If merges dir already exists or we fail to create it.
734    * @see HRegionFileSystem#cleanupMergesDir()
735    */
736   void createMergesDir() throws IOException {
737     Path mergesdir = getMergesDir();
738     if (fs.exists(mergesdir)) {
739       LOG.info("The " + mergesdir
740           + " directory exists.  Hence deleting it to recreate it");
741       if (!fs.delete(mergesdir, true)) {
742         throw new IOException("Failed deletion of " + mergesdir
743             + " before creating them again.");
744       }
745     }
746     if (!mkdirs(fs, conf, mergesdir))
747       throw new IOException("Failed create of " + mergesdir);
748   }
749 
750   /**
751    * Write out a merge reference under the given merges directory. Package local
752    * so it doesnt leak out of regionserver.
753    * @param mergedRegion {@link HRegionInfo} of the merged region
754    * @param familyName Column Family Name
755    * @param f File to create reference.
756    * @param mergedDir
757    * @return Path to created reference.
758    * @throws IOException
759    */
760   Path mergeStoreFile(final HRegionInfo mergedRegion, final String familyName,
761       final StoreFile f, final Path mergedDir)
762       throws IOException {
763     Path referenceDir = new Path(new Path(mergedDir,
764         mergedRegion.getEncodedName()), familyName);
765     // A whole reference to the store file.
766     Reference r = Reference.createTopReference(regionInfoForFs.getStartKey());
767     // Add the referred-to regions name as a dot separated suffix.
768     // See REF_NAME_REGEX regex above. The referred-to regions name is
769     // up in the path of the passed in <code>f</code> -- parentdir is family,
770     // then the directory above is the region name.
771     String mergingRegionName = regionInfoForFs.getEncodedName();
772     // Write reference with same file id only with the other region name as
773     // suffix and into the new region location (under same family).
774     Path p = new Path(referenceDir, f.getPath().getName() + "."
775         + mergingRegionName);
776     return r.write(fs, p);
777   }
778 
779   /**
780    * Commit a merged region, moving it from the merges temporary directory to
781    * the proper location in the filesystem.
782    * @param mergedRegionInfo merged region {@link HRegionInfo}
783    * @throws IOException
784    */
785   void commitMergedRegion(final HRegionInfo mergedRegionInfo) throws IOException {
786     Path regionDir = new Path(this.tableDir, mergedRegionInfo.getEncodedName());
787     Path mergedRegionTmpDir = this.getMergesDir(mergedRegionInfo);
788     // Move the tmp dir in the expected location
789     if (mergedRegionTmpDir != null && fs.exists(mergedRegionTmpDir)) {
790       if (!fs.rename(mergedRegionTmpDir, regionDir)) {
791         throw new IOException("Unable to rename " + mergedRegionTmpDir + " to "
792             + regionDir);
793       }
794     }
795   }
796 
797   // ===========================================================================
798   //  Create/Open/Delete Helpers
799   // ===========================================================================
800   /**
801    * Log the current state of the region
802    * @param LOG log to output information
803    * @throws IOException if an unexpected exception occurs
804    */
805   void logFileSystemState(final Log LOG) throws IOException {
806     FSUtils.logFileSystemState(fs, this.getRegionDir(), LOG);
807   }
808 
809   /**
810    * @param hri
811    * @return Content of the file we write out to the filesystem under a region
812    * @throws IOException
813    */
814   private static byte[] getRegionInfoFileContent(final HRegionInfo hri) throws IOException {
815     return hri.toDelimitedByteArray();
816   }
817 
818   /**
819    * Create a {@link HRegionInfo} from the serialized version on-disk.
820    * @param fs {@link FileSystem} that contains the Region Info file
821    * @param regionDir {@link Path} to the Region Directory that contains the Info file
822    * @return An {@link HRegionInfo} instance gotten from the Region Info file.
823    * @throws IOException if an error occurred during file open/read operation.
824    */
825   public static HRegionInfo loadRegionInfoFileContent(final FileSystem fs, final Path regionDir)
826       throws IOException {
827     FSDataInputStream in = fs.open(new Path(regionDir, REGION_INFO_FILE));
828     try {
829       return HRegionInfo.parseFrom(in);
830     } finally {
831       in.close();
832     }
833   }
834 
835   /**
836    * Write the .regioninfo file on-disk.
837    */
838   private static void writeRegionInfoFileContent(final Configuration conf, final FileSystem fs,
839       final Path regionInfoFile, final byte[] content) throws IOException {
840     // First check to get the permissions
841     FsPermission perms = FSUtils.getFilePermissions(fs, conf, HConstants.DATA_FILE_UMASK_KEY);
842     // Write the RegionInfo file content
843     FSDataOutputStream out = FSUtils.create(conf, fs, regionInfoFile, perms, null);
844     try {
845       out.write(content);
846     } finally {
847       out.close();
848     }
849   }
850 
851   /**
852    * Write out an info file under the stored region directory. Useful recovering mangled regions.
853    * If the regionInfo already exists on-disk, then we fast exit.
854    */
855   void checkRegionInfoOnFilesystem() throws IOException {
856     // Compose the content of the file so we can compare to length in filesystem. If not same,
857     // rewrite it (it may have been written in the old format using Writables instead of pb). The
858     // pb version is much shorter -- we write now w/o the toString version -- so checking length
859     // only should be sufficient. I don't want to read the file every time to check if it pb
860     // serialized.
861     byte[] content = getRegionInfoFileContent(regionInfoForFs);
862 
863     // Verify if the region directory exists before opening a region. We need to do this since if
864     // the region directory doesn't exist we will re-create the region directory and a new HRI
865     // when HRegion.openHRegion() is called.
866     try {
867       FileStatus status = fs.getFileStatus(getRegionDir());
868     } catch (FileNotFoundException e) {
869       LOG.warn(getRegionDir() + " doesn't exist for region: " + regionInfoForFs.getEncodedName() +
870           " on table " + regionInfo.getTable());
871     }
872 
873     try {
874       Path regionInfoFile = new Path(getRegionDir(), REGION_INFO_FILE);
875       FileStatus status = fs.getFileStatus(regionInfoFile);
876       if (status != null && status.getLen() == content.length) {
877         // Then assume the content good and move on.
878         // NOTE: that the length is not sufficient to define the the content matches.
879         return;
880       }
881 
882       LOG.info("Rewriting .regioninfo file at: " + regionInfoFile);
883       if (!fs.delete(regionInfoFile, false)) {
884         throw new IOException("Unable to remove existing " + regionInfoFile);
885       }
886     } catch (FileNotFoundException e) {
887       LOG.warn(REGION_INFO_FILE + " file not found for region: " + regionInfoForFs.getEncodedName() +
888           " on table " + regionInfo.getTable());
889     }
890 
891     // Write HRI to a file in case we need to recover hbase:meta
892     writeRegionInfoOnFilesystem(content, true);
893   }
894 
895   /**
896    * Write out an info file under the region directory. Useful recovering mangled regions.
897    * @param useTempDir indicate whether or not using the region .tmp dir for a safer file creation.
898    */
899   private void writeRegionInfoOnFilesystem(boolean useTempDir) throws IOException {
900     byte[] content = getRegionInfoFileContent(regionInfoForFs);
901     writeRegionInfoOnFilesystem(content, useTempDir);
902   }
903 
904   /**
905    * Write out an info file under the region directory. Useful recovering mangled regions.
906    * @param regionInfoContent serialized version of the {@link HRegionInfo}
907    * @param useTempDir indicate whether or not using the region .tmp dir for a safer file creation.
908    */
909   private void writeRegionInfoOnFilesystem(final byte[] regionInfoContent,
910       final boolean useTempDir) throws IOException {
911     Path regionInfoFile = new Path(getRegionDir(), REGION_INFO_FILE);
912     if (useTempDir) {
913       // Create in tmpDir and then move into place in case we crash after
914       // create but before close. If we don't successfully close the file,
915       // subsequent region reopens will fail the below because create is
916       // registered in NN.
917 
918       // And then create the file
919       Path tmpPath = new Path(getTempDir(), REGION_INFO_FILE);
920 
921       // If datanode crashes or if the RS goes down just before the close is called while trying to
922       // close the created regioninfo file in the .tmp directory then on next
923       // creation we will be getting AlreadyCreatedException.
924       // Hence delete and create the file if exists.
925       if (FSUtils.isExists(fs, tmpPath)) {
926         FSUtils.delete(fs, tmpPath, true);
927       }
928 
929       // Write HRI to a file in case we need to recover hbase:meta
930       writeRegionInfoFileContent(conf, fs, tmpPath, regionInfoContent);
931 
932       // Move the created file to the original path
933       if (fs.exists(tmpPath) &&  !rename(tmpPath, regionInfoFile)) {
934         throw new IOException("Unable to rename " + tmpPath + " to " + regionInfoFile);
935       }
936     } else {
937       // Write HRI to a file in case we need to recover hbase:meta
938       writeRegionInfoFileContent(conf, fs, regionInfoFile, regionInfoContent);
939     }
940   }
941 
942   /**
943    * Create a new Region on file-system.
944    * @param conf the {@link Configuration} to use
945    * @param fs {@link FileSystem} from which to add the region
946    * @param tableDir {@link Path} to where the table is being stored
947    * @param regionInfo {@link HRegionInfo} for region to be added
948    * @throws IOException if the region creation fails due to a FileSystem exception.
949    */
950   public static HRegionFileSystem createRegionOnFileSystem(final Configuration conf,
951       final FileSystem fs, final Path tableDir, final HRegionInfo regionInfo) throws IOException {
952     HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tableDir, regionInfo);
953 
954     // We only create a .regioninfo and the region directory if this is the default region replica
955     if (regionInfo.getReplicaId() == HRegionInfo.DEFAULT_REPLICA_ID) {
956       Path regionDir = regionFs.getRegionDir();
957       if (fs.exists(regionDir)) {
958         LOG.warn("Trying to create a region that already exists on disk: " + regionDir);
959         throw new IOException("The specified region already exists on disk: " + regionDir);
960       }
961 
962       // Create the region directory
963       if (!createDirOnFileSystem(fs, conf, regionDir)) {
964         LOG.warn("Unable to create the region directory: " + regionDir);
965         throw new IOException("Unable to create region directory: " + regionDir);
966       }
967 
968       // Write HRI to a file in case we need to recover hbase:meta
969       regionFs.writeRegionInfoOnFilesystem(false);
970     } else {
971       if (LOG.isDebugEnabled())
972         LOG.debug("Skipping creation of .regioninfo file for " + regionInfo);
973     }
974     return regionFs;
975   }
976 
977   /**
978    * Open Region from file-system.
979    * @param conf the {@link Configuration} to use
980    * @param fs {@link FileSystem} from which to add the region
981    * @param tableDir {@link Path} to where the table is being stored
982    * @param regionInfo {@link HRegionInfo} for region to be added
983    * @param readOnly True if you don't want to edit the region data
984    * @throws IOException if the region creation fails due to a FileSystem exception.
985    */
986   public static HRegionFileSystem openRegionFromFileSystem(final Configuration conf,
987       final FileSystem fs, final Path tableDir, final HRegionInfo regionInfo, boolean readOnly)
988       throws IOException {
989     HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tableDir, regionInfo);
990     Path regionDir = regionFs.getRegionDir();
991 
992     if (!fs.exists(regionDir)) {
993       LOG.warn("Trying to open a region that do not exists on disk: " + regionDir);
994       throw new IOException("The specified region do not exists on disk: " + regionDir);
995     }
996 
997     if (!readOnly) {
998       // Cleanup temporary directories
999       regionFs.cleanupTempDir();
1000       regionFs.cleanupSplitsDir();
1001       regionFs.cleanupMergesDir();
1002 
1003       // If it doesn't exists, Write HRI to a file, in case we need to recover hbase:meta
1004       // Only create HRI if we are the default replica
1005       if (regionInfo.getReplicaId() == HRegionInfo.DEFAULT_REPLICA_ID) {
1006         regionFs.checkRegionInfoOnFilesystem();
1007       } else {
1008         if (LOG.isDebugEnabled()) {
1009           LOG.debug("Skipping creation of .regioninfo file for " + regionInfo);
1010         }
1011       }
1012     }
1013 
1014     return regionFs;
1015   }
1016 
1017   /**
1018    * Remove the region from the table directory, archiving the region's hfiles.
1019    * @param conf the {@link Configuration} to use
1020    * @param fs {@link FileSystem} from which to remove the region
1021    * @param tableDir {@link Path} to where the table is being stored
1022    * @param regionInfo {@link HRegionInfo} for region to be deleted
1023    * @throws IOException if the request cannot be completed
1024    */
1025   public static void deleteRegionFromFileSystem(final Configuration conf,
1026       final FileSystem fs, final Path tableDir, final HRegionInfo regionInfo) throws IOException {
1027     HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tableDir, regionInfo);
1028     Path regionDir = regionFs.getRegionDir();
1029 
1030     if (!fs.exists(regionDir)) {
1031       LOG.warn("Trying to delete a region that do not exists on disk: " + regionDir);
1032       return;
1033     }
1034 
1035     if (LOG.isDebugEnabled()) {
1036       LOG.debug("DELETING region " + regionDir);
1037     }
1038 
1039     // Archive region
1040     Path rootDir = FSUtils.getRootDir(conf);
1041     HFileArchiver.archiveRegion(fs, rootDir, tableDir, regionDir);
1042 
1043     // Delete empty region dir
1044     if (!fs.delete(regionDir, true)) {
1045       LOG.warn("Failed delete of " + regionDir);
1046     }
1047   }
1048 
1049   /**
1050    * Creates a directory. Assumes the user has already checked for this directory existence.
1051    * @param dir
1052    * @return the result of fs.mkdirs(). In case underlying fs throws an IOException, it checks
1053    *         whether the directory exists or not, and returns true if it exists.
1054    * @throws IOException
1055    */
1056   boolean createDir(Path dir) throws IOException {
1057     int i = 0;
1058     IOException lastIOE = null;
1059     do {
1060       try {
1061         return mkdirs(fs, conf, dir);
1062       } catch (IOException ioe) {
1063         lastIOE = ioe;
1064         if (fs.exists(dir)) return true; // directory is present
1065         try {
1066           sleepBeforeRetry("Create Directory", i+1);
1067         } catch (InterruptedException e) {
1068           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
1069         }
1070       }
1071     } while (++i <= hdfsClientRetriesNumber);
1072     throw new IOException("Exception in createDir", lastIOE);
1073   }
1074 
1075   /**
1076    * Renames a directory. Assumes the user has already checked for this directory existence.
1077    * @param srcpath
1078    * @param dstPath
1079    * @return true if rename is successful.
1080    * @throws IOException
1081    */
1082   boolean rename(Path srcpath, Path dstPath) throws IOException {
1083     IOException lastIOE = null;
1084     int i = 0;
1085     do {
1086       try {
1087         return fs.rename(srcpath, dstPath);
1088       } catch (IOException ioe) {
1089         lastIOE = ioe;
1090         if (!fs.exists(srcpath) && fs.exists(dstPath)) return true; // successful move
1091         // dir is not there, retry after some time.
1092         try {
1093           sleepBeforeRetry("Rename Directory", i+1);
1094         } catch (InterruptedException e) {
1095           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
1096         }
1097       }
1098     } while (++i <= hdfsClientRetriesNumber);
1099 
1100     throw new IOException("Exception in rename", lastIOE);
1101   }
1102 
1103   /**
1104    * Deletes a directory. Assumes the user has already checked for this directory existence.
1105    * @param dir
1106    * @return true if the directory is deleted.
1107    * @throws IOException
1108    */
1109   boolean deleteDir(Path dir) throws IOException {
1110     IOException lastIOE = null;
1111     int i = 0;
1112     do {
1113       try {
1114         return fs.delete(dir, true);
1115       } catch (IOException ioe) {
1116         lastIOE = ioe;
1117         if (!fs.exists(dir)) return true;
1118         // dir is there, retry deleting after some time.
1119         try {
1120           sleepBeforeRetry("Delete Directory", i+1);
1121         } catch (InterruptedException e) {
1122           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
1123         }
1124       }
1125     } while (++i <= hdfsClientRetriesNumber);
1126 
1127     throw new IOException("Exception in DeleteDir", lastIOE);
1128   }
1129 
1130   /**
1131    * sleeping logic; handles the interrupt exception.
1132    */
1133   private void sleepBeforeRetry(String msg, int sleepMultiplier) throws InterruptedException {
1134     sleepBeforeRetry(msg, sleepMultiplier, baseSleepBeforeRetries, hdfsClientRetriesNumber);
1135   }
1136 
1137   /**
1138    * Creates a directory for a filesystem and configuration object. Assumes the user has already
1139    * checked for this directory existence.
1140    * @param fs
1141    * @param conf
1142    * @param dir
1143    * @return the result of fs.mkdirs(). In case underlying fs throws an IOException, it checks
1144    *         whether the directory exists or not, and returns true if it exists.
1145    * @throws IOException
1146    */
1147   private static boolean createDirOnFileSystem(FileSystem fs, Configuration conf, Path dir)
1148       throws IOException {
1149     int i = 0;
1150     IOException lastIOE = null;
1151     int hdfsClientRetriesNumber = conf.getInt("hdfs.client.retries.number",
1152       DEFAULT_HDFS_CLIENT_RETRIES_NUMBER);
1153     int baseSleepBeforeRetries = conf.getInt("hdfs.client.sleep.before.retries",
1154       DEFAULT_BASE_SLEEP_BEFORE_RETRIES);
1155     do {
1156       try {
1157         return fs.mkdirs(dir);
1158       } catch (IOException ioe) {
1159         lastIOE = ioe;
1160         if (fs.exists(dir)) return true; // directory is present
1161         try {
1162           sleepBeforeRetry("Create Directory", i+1, baseSleepBeforeRetries, hdfsClientRetriesNumber);
1163         } catch (InterruptedException e) {
1164           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
1165         }
1166       }
1167     } while (++i <= hdfsClientRetriesNumber);
1168 
1169     throw new IOException("Exception in createDir", lastIOE);
1170   }
1171 
1172   /**
1173    * sleeping logic for static methods; handles the interrupt exception. Keeping a static version
1174    * for this to avoid re-looking for the integer values.
1175    */
1176   private static void sleepBeforeRetry(String msg, int sleepMultiplier, int baseSleepBeforeRetries,
1177       int hdfsClientRetriesNumber) throws InterruptedException {
1178     if (sleepMultiplier > hdfsClientRetriesNumber) {
1179       if (LOG.isDebugEnabled()) {
1180         LOG.debug(msg + ", retries exhausted");
1181       }
1182       return;
1183     }
1184     if (LOG.isDebugEnabled()) {
1185       LOG.debug(msg + ", sleeping " + baseSleepBeforeRetries + " times " + sleepMultiplier);
1186     }
1187     Thread.sleep((long)baseSleepBeforeRetries * sleepMultiplier);
1188   }
1189 }