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