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