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.util;
019
020import static org.apache.hadoop.hbase.util.LocatedBlockHelper.getLocatedBlockLocations;
021import static org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction.SAFEMODE_GET;
022
023import edu.umd.cs.findbugs.annotations.CheckForNull;
024import java.io.ByteArrayInputStream;
025import java.io.DataInputStream;
026import java.io.EOFException;
027import java.io.FileNotFoundException;
028import java.io.IOException;
029import java.io.InterruptedIOException;
030import java.lang.reflect.InvocationTargetException;
031import java.lang.reflect.Method;
032import java.net.InetSocketAddress;
033import java.net.URI;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.Collection;
037import java.util.Collections;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Locale;
043import java.util.Map;
044import java.util.Optional;
045import java.util.Set;
046import java.util.Vector;
047import java.util.concurrent.ConcurrentHashMap;
048import java.util.concurrent.ExecutionException;
049import java.util.concurrent.ExecutorService;
050import java.util.concurrent.Executors;
051import java.util.concurrent.Future;
052import java.util.concurrent.FutureTask;
053import java.util.concurrent.ThreadPoolExecutor;
054import java.util.concurrent.TimeUnit;
055import java.util.regex.Pattern;
056import org.apache.commons.lang3.ArrayUtils;
057import org.apache.hadoop.conf.Configuration;
058import org.apache.hadoop.fs.BlockLocation;
059import org.apache.hadoop.fs.FSDataInputStream;
060import org.apache.hadoop.fs.FSDataOutputStream;
061import org.apache.hadoop.fs.FileStatus;
062import org.apache.hadoop.fs.FileSystem;
063import org.apache.hadoop.fs.FileUtil;
064import org.apache.hadoop.fs.Path;
065import org.apache.hadoop.fs.PathFilter;
066import org.apache.hadoop.fs.StorageType;
067import org.apache.hadoop.fs.permission.FsPermission;
068import org.apache.hadoop.hbase.ActiveClusterSuffix;
069import org.apache.hadoop.hbase.ClusterIdFile;
070import org.apache.hadoop.hbase.ClusterIdFileParser;
071import org.apache.hadoop.hbase.HConstants;
072import org.apache.hadoop.hbase.HDFSBlocksDistribution;
073import org.apache.hadoop.hbase.TableName;
074import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
075import org.apache.hadoop.hbase.client.RegionInfo;
076import org.apache.hadoop.hbase.client.RegionInfoBuilder;
077import org.apache.hadoop.hbase.exceptions.DeserializationException;
078import org.apache.hadoop.hbase.fs.HFileSystem;
079import org.apache.hadoop.hbase.io.HFileLink;
080import org.apache.hadoop.hbase.master.HMaster;
081import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
082import org.apache.hadoop.hdfs.DFSClient;
083import org.apache.hadoop.hdfs.DFSHedgedReadMetrics;
084import org.apache.hadoop.hdfs.DFSUtil;
085import org.apache.hadoop.hdfs.DistributedFileSystem;
086import org.apache.hadoop.hdfs.client.HdfsDataInputStream;
087import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
088import org.apache.hadoop.hdfs.protocol.LocatedBlock;
089import org.apache.hadoop.io.IOUtils;
090import org.apache.hadoop.ipc.RemoteException;
091import org.apache.yetus.audience.InterfaceAudience;
092import org.slf4j.Logger;
093import org.slf4j.LoggerFactory;
094
095import org.apache.hbase.thirdparty.com.google.common.base.Throwables;
096import org.apache.hbase.thirdparty.com.google.common.collect.Iterators;
097import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
098import org.apache.hbase.thirdparty.com.google.common.primitives.Ints;
099import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder;
100
101import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
102import org.apache.hadoop.hbase.shaded.protobuf.generated.FSProtos;
103
104/**
105 * Utility methods for interacting with the underlying file system.
106 */
107@InterfaceAudience.Private
108public final class FSUtils {
109  private static final Logger LOG = LoggerFactory.getLogger(FSUtils.class);
110
111  private static final String THREAD_POOLSIZE = "hbase.client.localityCheck.threadPoolSize";
112  private static final int DEFAULT_THREAD_POOLSIZE = 2;
113
114  /** Set to true on Windows platforms */
115  // currently only used in testing. TODO refactor into a test class
116  public static final boolean WINDOWS = System.getProperty("os.name").startsWith("Windows");
117
118  private static Class<?> safeModeClazz = null;
119  private static Class<?> safeModeActionClazz = null;
120  private static Object safeModeGet = null;
121  {
122    try {
123      safeModeClazz = Class.forName("org.apache.hadoop.fs.SafeMode");
124      safeModeActionClazz = Class.forName("org.apache.hadoop.fs.SafeModeAction");
125      safeModeGet = safeModeClazz.getField("SAFEMODE_GET").get(null);
126    } catch (ClassNotFoundException | NoSuchFieldException e) {
127      LOG.debug("SafeMode interface not in the classpath, this means Hadoop 3.3.5 or below.");
128    } catch (IllegalAccessException e) {
129      LOG.error("SafeModeAction.SAFEMODE_GET is not accessible. "
130        + "Unexpected Hadoop version or messy classpath?", e);
131      throw new RuntimeException(e);
132    }
133  }
134
135  private FSUtils() {
136  }
137
138  /** Returns True is <code>fs</code> is instance of DistributedFileSystem n */
139  public static boolean isDistributedFileSystem(final FileSystem fs) throws IOException {
140    FileSystem fileSystem = fs;
141    // If passed an instance of HFileSystem, it fails instanceof DistributedFileSystem.
142    // Check its backing fs for dfs-ness.
143    if (fs instanceof HFileSystem) {
144      fileSystem = ((HFileSystem) fs).getBackingFs();
145    }
146    return fileSystem instanceof DistributedFileSystem;
147  }
148
149  /**
150   * Compare path component of the Path URI; e.g. if hdfs://a/b/c and /a/b/c, it will compare the
151   * '/a/b/c' part. If you passed in 'hdfs://a/b/c and b/c, it would return true. Does not consider
152   * schema; i.e. if schemas different but path or subpath matches, the two will equate.
153   * @param pathToSearch Path we will be trying to match.
154   * @return True if <code>pathTail</code> is tail on the path of <code>pathToSearch</code>
155   */
156  public static boolean isMatchingTail(final Path pathToSearch, final Path pathTail) {
157    Path tailPath = pathTail;
158    String tailName;
159    Path toSearch = pathToSearch;
160    String toSearchName;
161    boolean result = false;
162
163    if (pathToSearch.depth() != pathTail.depth()) {
164      return false;
165    }
166
167    do {
168      tailName = tailPath.getName();
169      if (tailName == null || tailName.isEmpty()) {
170        result = true;
171        break;
172      }
173      toSearchName = toSearch.getName();
174      if (toSearchName == null || toSearchName.isEmpty()) {
175        break;
176      }
177      // Move up a parent on each path for next go around. Path doesn't let us go off the end.
178      tailPath = tailPath.getParent();
179      toSearch = toSearch.getParent();
180    } while (tailName.equals(toSearchName));
181    return result;
182  }
183
184  /**
185   * Delete the region directory if exists.
186   * @return True if deleted the region directory.
187   */
188  public static boolean deleteRegionDir(final Configuration conf, final RegionInfo hri)
189    throws IOException {
190    Path rootDir = CommonFSUtils.getRootDir(conf);
191    FileSystem fs = rootDir.getFileSystem(conf);
192    return CommonFSUtils.deleteDirectory(fs,
193      new Path(CommonFSUtils.getTableDir(rootDir, hri.getTable()), hri.getEncodedName()));
194  }
195
196  /**
197   * Create the specified file on the filesystem. By default, this will:
198   * <ol>
199   * <li>overwrite the file if it exists</li>
200   * <li>apply the umask in the configuration (if it is enabled)</li>
201   * <li>use the fs configured buffer size (or 4096 if not set)</li>
202   * <li>use the configured column family replication or default replication if
203   * {@link ColumnFamilyDescriptorBuilder#DEFAULT_DFS_REPLICATION}</li>
204   * <li>use the default block size</li>
205   * <li>not track progress</li>
206   * </ol>
207   * @param conf         configurations
208   * @param fs           {@link FileSystem} on which to write the file
209   * @param path         {@link Path} to the file to write
210   * @param perm         permissions
211   * @param favoredNodes favored data nodes
212   * @return output stream to the created file
213   * @throws IOException if the file cannot be created
214   */
215  public static FSDataOutputStream create(Configuration conf, FileSystem fs, Path path,
216    FsPermission perm, InetSocketAddress[] favoredNodes) throws IOException {
217    return create(conf, fs, path, perm, favoredNodes, true);
218  }
219
220  /**
221   * Create the specified file on the filesystem. By default, this will:
222   * <ol>
223   * <li>overwrite the file if it exists</li>
224   * <li>apply the umask in the configuration (if it is enabled)</li>
225   * <li>use the fs configured buffer size (or 4096 if not set)</li>
226   * <li>use the configured column family replication or default replication if
227   * {@link ColumnFamilyDescriptorBuilder#DEFAULT_DFS_REPLICATION}</li>
228   * <li>use the default block size</li>
229   * <li>not track progress</li>
230   * </ol>
231   * @param conf              configurations
232   * @param fs                {@link FileSystem} on which to write the file
233   * @param path              {@link Path} to the file to write
234   * @param perm              permissions
235   * @param favoredNodes      favored data nodes
236   * @param isRecursiveCreate recursively create parent directories
237   * @return output stream to the created file
238   * @throws IOException if the file cannot be created
239   */
240  public static FSDataOutputStream create(Configuration conf, FileSystem fs, Path path,
241    FsPermission perm, InetSocketAddress[] favoredNodes, boolean isRecursiveCreate)
242    throws IOException {
243    if (fs instanceof HFileSystem) {
244      FileSystem backingFs = ((HFileSystem) fs).getBackingFs();
245      if (backingFs instanceof DistributedFileSystem) {
246        short replication = Short.parseShort(conf.get(ColumnFamilyDescriptorBuilder.DFS_REPLICATION,
247          String.valueOf(ColumnFamilyDescriptorBuilder.DEFAULT_DFS_REPLICATION)));
248        DistributedFileSystem.HdfsDataOutputStreamBuilder builder =
249          ((DistributedFileSystem) backingFs).createFile(path).recursive().permission(perm)
250            .create();
251        if (favoredNodes != null) {
252          builder.favoredNodes(favoredNodes);
253        }
254        if (replication > 0) {
255          builder.replication(replication);
256        }
257        return builder.build();
258      }
259
260    }
261    return CommonFSUtils.create(fs, path, perm, true, isRecursiveCreate);
262  }
263
264  /**
265   * Checks to see if the specified file system is available
266   * @param fs filesystem
267   * @throws IOException e
268   */
269  public static void checkFileSystemAvailable(final FileSystem fs) throws IOException {
270    if (!(fs instanceof DistributedFileSystem)) {
271      return;
272    }
273    IOException exception = null;
274    DistributedFileSystem dfs = (DistributedFileSystem) fs;
275    try {
276      if (dfs.exists(new Path("/"))) {
277        return;
278      }
279    } catch (IOException e) {
280      exception = e instanceof RemoteException ? ((RemoteException) e).unwrapRemoteException() : e;
281    }
282    try {
283      fs.close();
284    } catch (Exception e) {
285      LOG.error("file system close failed: ", e);
286    }
287    throw new IOException("File system is not available", exception);
288  }
289
290  /**
291   * Inquire the Active NameNode's safe mode status.
292   * @param dfs A DistributedFileSystem object representing the underlying HDFS.
293   * @return whether we're in safe mode
294   */
295  private static boolean isInSafeMode(FileSystem dfs) throws IOException {
296    if (isDistributedFileSystem(dfs)) {
297      return ((DistributedFileSystem) dfs).setSafeMode(SAFEMODE_GET, true);
298    } else {
299      try {
300        Object ret = dfs.getClass()
301          .getMethod("setSafeMode", new Class[] { safeModeActionClazz, Boolean.class })
302          .invoke(dfs, safeModeGet, true);
303        return (Boolean) ret;
304      } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
305        LOG.error("The file system does not support setSafeMode(). Abort.", e);
306        throw new RuntimeException(e);
307      }
308    }
309  }
310
311  /**
312   * Check whether dfs is in safemode.
313   */
314  public static void checkDfsSafeMode(final Configuration conf) throws IOException {
315    boolean isInSafeMode = false;
316    FileSystem fs = FileSystem.get(conf);
317    if (supportSafeMode(fs)) {
318      isInSafeMode = isInSafeMode(fs);
319    }
320    if (isInSafeMode) {
321      throw new IOException("File system is in safemode, it can't be written now");
322    }
323  }
324
325  /**
326   * Verifies current version of file system
327   * @param fs      filesystem object
328   * @param rootdir root hbase directory
329   * @return null if no version file exists, version string otherwise
330   * @throws IOException              if the version file fails to open
331   * @throws DeserializationException if the version data cannot be translated into a version
332   */
333  public static String getVersion(FileSystem fs, Path rootdir)
334    throws IOException, DeserializationException {
335    final Path versionFile = new Path(rootdir, HConstants.VERSION_FILE_NAME);
336    FileStatus[] status = null;
337    try {
338      // hadoop 2.0 throws FNFE if directory does not exist.
339      // hadoop 1.0 returns null if directory does not exist.
340      status = fs.listStatus(versionFile);
341    } catch (FileNotFoundException fnfe) {
342      return null;
343    }
344    if (ArrayUtils.getLength(status) == 0) {
345      return null;
346    }
347    String version = null;
348    byte[] content = new byte[(int) status[0].getLen()];
349    FSDataInputStream s = fs.open(versionFile);
350    try {
351      IOUtils.readFully(s, content, 0, content.length);
352      if (ProtobufUtil.isPBMagicPrefix(content)) {
353        version = parseVersionFrom(content);
354      } else {
355        // Presume it pre-pb format.
356        try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(content))) {
357          version = dis.readUTF();
358        }
359      }
360    } catch (EOFException eof) {
361      LOG.warn("Version file was empty, odd, will try to set it.");
362    } finally {
363      s.close();
364    }
365    return version;
366  }
367
368  /**
369   * Parse the content of the ${HBASE_ROOTDIR}/hbase.version file.
370   * @param bytes The byte content of the hbase.version file
371   * @return The version found in the file as a String
372   * @throws DeserializationException if the version data cannot be translated into a version
373   */
374  static String parseVersionFrom(final byte[] bytes) throws DeserializationException {
375    ProtobufUtil.expectPBMagicPrefix(bytes);
376    int pblen = ProtobufUtil.lengthOfPBMagic();
377    FSProtos.HBaseVersionFileContent.Builder builder =
378      FSProtos.HBaseVersionFileContent.newBuilder();
379    try {
380      ProtobufUtil.mergeFrom(builder, bytes, pblen, bytes.length - pblen);
381      return builder.getVersion();
382    } catch (IOException e) {
383      // Convert
384      throw new DeserializationException(e);
385    }
386  }
387
388  /**
389   * Create the content to write into the ${HBASE_ROOTDIR}/hbase.version file.
390   * @param version Version to persist
391   * @return Serialized protobuf with <code>version</code> content and a bit of pb magic for a
392   *         prefix.
393   */
394  static byte[] toVersionByteArray(final String version) {
395    FSProtos.HBaseVersionFileContent.Builder builder =
396      FSProtos.HBaseVersionFileContent.newBuilder();
397    return ProtobufUtil.prependPBMagic(builder.setVersion(version).build().toByteArray());
398  }
399
400  /**
401   * Verifies current version of file system
402   * @param fs      file system
403   * @param rootdir root directory of HBase installation
404   * @param message if true, issues a message on System.out
405   * @throws IOException              if the version file cannot be opened
406   * @throws DeserializationException if the contents of the version file cannot be parsed
407   */
408  public static void checkVersion(FileSystem fs, Path rootdir, boolean message)
409    throws IOException, DeserializationException {
410    checkVersion(fs, rootdir, message, 0, HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS);
411  }
412
413  /**
414   * Verifies current version of file system
415   * @param fs      file system
416   * @param rootdir root directory of HBase installation
417   * @param message if true, issues a message on System.out
418   * @param wait    wait interval
419   * @param retries number of times to retry
420   * @throws IOException              if the version file cannot be opened
421   * @throws DeserializationException if the contents of the version file cannot be parsed
422   */
423  public static void checkVersion(FileSystem fs, Path rootdir, boolean message, int wait,
424    int retries) throws IOException, DeserializationException {
425    String version = getVersion(fs, rootdir);
426    String msg;
427    if (version == null) {
428      if (!metaRegionExists(fs, rootdir)) {
429        // rootDir is empty (no version file and no root region)
430        // just create new version file (HBASE-1195)
431        setVersion(fs, rootdir, wait, retries);
432        return;
433      } else {
434        msg = "hbase.version file is missing. Is your hbase.rootdir valid? "
435          + "You can restore hbase.version file by running 'HBCK2 filesystem -fix'. "
436          + "See https://github.com/apache/hbase-operator-tools/tree/master/hbase-hbck2";
437      }
438    } else if (version.compareTo(HConstants.FILE_SYSTEM_VERSION) == 0) {
439      return;
440    } else {
441      msg = "HBase file layout needs to be upgraded. Current filesystem version is " + version
442        + " but software requires version " + HConstants.FILE_SYSTEM_VERSION
443        + ". Consult https://hbase.apache.org/docs for further information about "
444        + "upgrading HBase.";
445    }
446
447    // version is deprecated require migration
448    // Output on stdout so user sees it in terminal.
449    if (message) {
450      System.out.println("WARNING! " + msg);
451    }
452    throw new FileSystemVersionException(msg);
453  }
454
455  /**
456   * Sets version of file system
457   * @param fs      filesystem object
458   * @param rootdir hbase root
459   * @throws IOException e
460   */
461  public static void setVersion(FileSystem fs, Path rootdir) throws IOException {
462    setVersion(fs, rootdir, HConstants.FILE_SYSTEM_VERSION, 0,
463      HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS);
464  }
465
466  /**
467   * Sets version of file system
468   * @param fs      filesystem object
469   * @param rootdir hbase root
470   * @param wait    time to wait for retry
471   * @param retries number of times to retry before failing
472   * @throws IOException e
473   */
474  public static void setVersion(FileSystem fs, Path rootdir, int wait, int retries)
475    throws IOException {
476    setVersion(fs, rootdir, HConstants.FILE_SYSTEM_VERSION, wait, retries);
477  }
478
479  /**
480   * Sets version of file system
481   * @param fs      filesystem object
482   * @param rootdir hbase root directory
483   * @param version version to set
484   * @param wait    time to wait for retry
485   * @param retries number of times to retry before throwing an IOException
486   * @throws IOException e
487   */
488  public static void setVersion(FileSystem fs, Path rootdir, String version, int wait, int retries)
489    throws IOException {
490    Path versionFile = new Path(rootdir, HConstants.VERSION_FILE_NAME);
491    Path tempVersionFile = new Path(rootdir,
492      HConstants.HBASE_TEMP_DIRECTORY + Path.SEPARATOR + HConstants.VERSION_FILE_NAME);
493    while (true) {
494      try {
495        // Write the version to a temporary file
496        FSDataOutputStream s = fs.create(tempVersionFile);
497        try {
498          s.write(toVersionByteArray(version));
499          s.close();
500          s = null;
501          // Move the temp version file to its normal location. Returns false
502          // if the rename failed. Throw an IOE in that case.
503          if (!fs.rename(tempVersionFile, versionFile)) {
504            throw new IOException("Unable to move temp version file to " + versionFile);
505          }
506        } finally {
507          // Cleaning up the temporary if the rename failed would be trying
508          // too hard. We'll unconditionally create it again the next time
509          // through anyway, files are overwritten by default by create().
510
511          // Attempt to close the stream on the way out if it is still open.
512          try {
513            if (s != null) s.close();
514          } catch (IOException ignore) {
515          }
516        }
517        LOG.info("Created version file at " + rootdir.toString() + " with version=" + version);
518        return;
519      } catch (IOException e) {
520        if (retries > 0) {
521          LOG.debug("Unable to create version file at " + rootdir.toString() + ", retrying", e);
522          fs.delete(versionFile, false);
523          try {
524            if (wait > 0) {
525              Thread.sleep(wait);
526            }
527          } catch (InterruptedException ie) {
528            throw (InterruptedIOException) new InterruptedIOException().initCause(ie);
529          }
530          retries--;
531        } else {
532          throw e;
533        }
534      }
535    }
536  }
537
538  /**
539   * Checks that a cluster ID file exists in the HBase root directory
540   * @param fs      the root directory FileSystem
541   * @param rootdir the HBase root directory in HDFS
542   * @param wait    how long to wait between retries
543   * @return <code>true</code> if the file exists, otherwise <code>false</code>
544   * @throws IOException if checking the FileSystem fails
545   */
546  public static boolean checkFileExistsInHbaseRootDir(FileSystem fs, Path rootdir, String file,
547    long wait) throws IOException {
548    while (true) {
549      try {
550        Path filePath = new Path(rootdir, file);
551        return fs.exists(filePath);
552      } catch (IOException ioe) {
553        if (wait > 0L) {
554          LOG.warn("Unable to check file {} in {}, retrying in {}ms", file, rootdir, wait, ioe);
555          try {
556            Thread.sleep(wait);
557          } catch (InterruptedException e) {
558            Thread.currentThread().interrupt();
559            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
560          }
561        } else {
562          throw ioe;
563        }
564      }
565    }
566  }
567
568  /**
569   * Use the given parser object to read and parse contents of Cluster Id file. e.g. Cluster Id or
570   * Active read-replica Cluster Id
571   */
572  public static <T extends ClusterIdFile> T getClusterIdFile(FileSystem fs, Path rootdir,
573    ClusterIdFileParser<T> parser) throws IOException {
574    Path idPath = new Path(rootdir, parser.getFileName());
575    T cs = null;
576    FileStatus status = fs.exists(idPath) ? fs.getFileStatus(idPath) : null;
577    if (status != null) {
578      int len = Ints.checkedCast(status.getLen());
579      byte[] content = new byte[len];
580      FSDataInputStream in = fs.open(idPath);
581      try {
582        in.readFully(content);
583      } catch (EOFException eof) {
584        LOG.warn("Cluster file {} is empty", idPath);
585      } finally {
586        in.close();
587      }
588      try {
589        cs = parser.parseFrom(content);
590      } catch (DeserializationException e) {
591        throw new IOException("content=" + Bytes.toString(content), e);
592      }
593      // If not pb'd, make it so.
594      if (!ProtobufUtil.isPBMagicPrefix(content)) {
595        String cid = null;
596        in = fs.open(idPath);
597        try {
598          cid = in.readUTF();
599          cs = parser.readString(cid);
600        } catch (EOFException eof) {
601          LOG.warn("Cluster file {} is empty", idPath);
602        } finally {
603          in.close();
604        }
605        rewriteAsPb(fs, rootdir, idPath, parser.getFileName(), cs);
606      }
607      return cs;
608    } else {
609      LOG.warn("Cluster file does not exist at {}", idPath);
610    }
611    return cs;
612  }
613
614  public static String getClusterIdFromActiveClusterFile(FileSystem fs, Path rootDir) {
615    String activeClusterId = null;
616    try {
617      ActiveClusterSuffix parsedClusterIdFile =
618        FSUtils.getClusterIdFile(fs, rootDir, new ActiveClusterSuffix.Parser());
619      if (parsedClusterIdFile != null) {
620        activeClusterId = parsedClusterIdFile.toString();
621      }
622    } catch (IOException e) {
623      LOG.debug("Failed to read active cluster ID from file", e);
624    }
625    return activeClusterId;
626  }
627
628  private static <T extends ClusterIdFile> void rewriteAsPb(final FileSystem fs, final Path rootdir,
629    final Path p, final String fileName, final T cs) throws IOException {
630    // Rewrite the file as pb. Move aside the old one first, write new
631    // then delete the moved-aside file.
632    Path movedAsideName = new Path(p + "." + EnvironmentEdgeManager.currentTime());
633    if (!fs.rename(p, movedAsideName)) throw new IOException("Failed rename of " + p);
634    setClusterIdFile(fs, rootdir, fileName, cs, 100);
635    if (!fs.delete(movedAsideName, false)) {
636      throw new IOException("Failed delete of " + movedAsideName);
637    }
638    LOG.debug("Rewrote the {} file as pb", fileName);
639  }
640
641  /**
642   * Writes a new unique identifier for this cluster to the Cluster Id ("hbase.id" or
643   * "active.cluster.suffix.id") file in the HBase root directory. If any operations on the ID file
644   * fails, and {@code wait} is a positive value, the method will retry to produce the ID file until
645   * the thread is forcibly interrupted.
646   * @param fs       the root directory FileSystem
647   * @param rootdir  the path to the HBase root directory
648   * @param fileName name of the file to be written
649   * @param cs       the object to be written
650   * @param wait     how long (in milliseconds) to wait between retries
651   * @throws IOException if writing to the FileSystem fails and no wait value
652   */
653  public static <T extends ClusterIdFile> void setClusterIdFile(final FileSystem fs,
654    final Path rootdir, final String fileName, final T cs, final long wait) throws IOException {
655    final Path idFile = new Path(rootdir, fileName);
656    final Path tempDir = new Path(rootdir, HConstants.HBASE_TEMP_DIRECTORY);
657    final Path tempIdFile = new Path(tempDir, fileName);
658
659    LOG.debug("Cluster file [{}] is present and contains cluster id: {}", idFile, cs);
660    writeClusterInfo(fs, rootdir, idFile, tempIdFile, cs.toByteArray(), wait);
661  }
662
663  /**
664   * Writes information about this cluster to the specified file. For ex, it is used for writing
665   * cluster id in "hbase.id" file in the HBase root directory. Also, used for writing active
666   * cluster suffix in "active_cluster_suffix.id" file. If any operations on the ID file fails, and
667   * {@code wait} is a positive value, the method will retry to produce the ID file until the thread
668   * is forcibly interrupted.
669   */
670  private static void writeClusterInfo(final FileSystem fs, final Path rootdir, final Path idFile,
671    final Path tempIdFile, byte[] fileData, final long wait) throws IOException {
672    while (true) {
673      Optional<IOException> failure = Optional.empty();
674
675      LOG.debug("Write the file to a temporary location: {}", tempIdFile);
676      try (FSDataOutputStream s = fs.create(tempIdFile)) {
677        s.write(fileData);
678      } catch (IOException ioe) {
679        failure = Optional.of(ioe);
680      }
681
682      if (!failure.isPresent()) {
683        try {
684          LOG.debug("Move the temporary file to its target location [{}]:[{}]", tempIdFile, idFile);
685
686          if (!fs.rename(tempIdFile, idFile)) {
687            failure = Optional.of(new IOException("Unable to move temp file to " + idFile));
688          }
689        } catch (IOException ioe) {
690          failure = Optional.of(ioe);
691        }
692      }
693
694      if (failure.isPresent()) {
695        final IOException cause = failure.get();
696        if (wait > 0L) {
697          LOG.warn("Unable to create file in {}, retrying in {}ms", rootdir, wait, cause);
698          try {
699            Thread.sleep(wait);
700          } catch (InterruptedException e) {
701            Thread.currentThread().interrupt();
702            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
703          }
704          continue;
705        } else {
706          throw cause;
707        }
708      } else {
709        return;
710      }
711    }
712  }
713
714  /**
715   * If DFS, check safe mode and if so, wait until we clear it.
716   * @param conf configuration
717   * @param wait Sleep between retries
718   * @throws IOException e
719   */
720  public static void waitOnSafeMode(final Configuration conf, final long wait) throws IOException {
721    FileSystem fs = FileSystem.get(conf);
722    if (!supportSafeMode(fs)) {
723      return;
724    }
725    // Make sure dfs is not in safe mode
726    while (isInSafeMode(fs)) {
727      LOG.info("Waiting for dfs to exit safe mode...");
728      try {
729        Thread.sleep(wait);
730      } catch (InterruptedException e) {
731        Thread.currentThread().interrupt();
732        throw (InterruptedIOException) new InterruptedIOException().initCause(e);
733      }
734    }
735  }
736
737  public static boolean supportSafeMode(FileSystem fs) {
738    // return true if HDFS.
739    if (fs instanceof DistributedFileSystem) {
740      return true;
741    }
742    // return true if the file system implements SafeMode interface.
743    if (safeModeClazz != null) {
744      return (safeModeClazz.isAssignableFrom(fs.getClass()));
745    }
746    // return false if the file system is not HDFS and does not implement SafeMode interface.
747    return false;
748  }
749
750  /**
751   * Checks if meta region exists
752   * @param fs      file system
753   * @param rootDir root directory of HBase installation
754   * @return true if exists
755   */
756  public static boolean metaRegionExists(FileSystem fs, Path rootDir) throws IOException {
757    Path metaRegionDir = getRegionDirFromRootDir(rootDir, RegionInfoBuilder.FIRST_META_REGIONINFO);
758    return fs.exists(metaRegionDir);
759  }
760
761  /**
762   * Compute HDFS block distribution of a given HdfsDataInputStream. All HdfsDataInputStreams are
763   * backed by a series of LocatedBlocks, which are fetched periodically from the namenode. This
764   * method retrieves those blocks from the input stream and uses them to calculate
765   * HDFSBlockDistribution. The underlying method in DFSInputStream does attempt to use locally
766   * cached blocks, but may hit the namenode if the cache is determined to be incomplete. The method
767   * also involves making copies of all LocatedBlocks rather than return the underlying blocks
768   * themselves.
769   */
770  static public HDFSBlocksDistribution
771    computeHDFSBlocksDistribution(HdfsDataInputStream inputStream) throws IOException {
772    List<LocatedBlock> blocks = inputStream.getAllBlocks();
773    HDFSBlocksDistribution blocksDistribution = new HDFSBlocksDistribution();
774    for (LocatedBlock block : blocks) {
775      String[] hosts = getHostsForLocations(block);
776      long len = block.getBlockSize();
777      StorageType[] storageTypes = block.getStorageTypes();
778      blocksDistribution.addHostsAndBlockWeight(hosts, len, storageTypes);
779    }
780    return blocksDistribution;
781  }
782
783  private static String[] getHostsForLocations(LocatedBlock block) {
784    DatanodeInfo[] locations = getLocatedBlockLocations(block);
785    String[] hosts = new String[locations.length];
786    for (int i = 0; i < hosts.length; i++) {
787      hosts[i] = locations[i].getHostName();
788    }
789    return hosts;
790  }
791
792  /**
793   * Compute HDFS blocks distribution of a given file, or a portion of the file
794   * @param fs     file system
795   * @param status file status of the file
796   * @param start  start position of the portion
797   * @param length length of the portion
798   * @return The HDFS blocks distribution
799   */
800  static public HDFSBlocksDistribution computeHDFSBlocksDistribution(final FileSystem fs,
801    FileStatus status, long start, long length) throws IOException {
802    HDFSBlocksDistribution blocksDistribution = new HDFSBlocksDistribution();
803    BlockLocation[] blockLocations = fs.getFileBlockLocations(status, start, length);
804    addToHDFSBlocksDistribution(blocksDistribution, blockLocations);
805    return blocksDistribution;
806  }
807
808  /**
809   * Update blocksDistribution with blockLocations
810   * @param blocksDistribution the hdfs blocks distribution
811   * @param blockLocations     an array containing block location
812   */
813  static public void addToHDFSBlocksDistribution(HDFSBlocksDistribution blocksDistribution,
814    BlockLocation[] blockLocations) throws IOException {
815    for (BlockLocation bl : blockLocations) {
816      String[] hosts = bl.getHosts();
817      long len = bl.getLength();
818      StorageType[] storageTypes = bl.getStorageTypes();
819      blocksDistribution.addHostsAndBlockWeight(hosts, len, storageTypes);
820    }
821  }
822
823  // TODO move this method OUT of FSUtils. No dependencies to HMaster
824  /**
825   * Returns the total overall fragmentation percentage. Includes hbase:meta and -ROOT- as well.
826   * @param master The master defining the HBase root and file system
827   * @return A map for each table and its percentage (never null)
828   * @throws IOException When scanning the directory fails
829   */
830  public static int getTotalTableFragmentation(final HMaster master) throws IOException {
831    Map<String, Integer> map = getTableFragmentation(master);
832    return map.isEmpty() ? -1 : map.get("-TOTAL-");
833  }
834
835  /**
836   * Runs through the HBase rootdir and checks how many stores for each table have more than one
837   * file in them. Checks -ROOT- and hbase:meta too. The total percentage across all tables is
838   * stored under the special key "-TOTAL-".
839   * @param master The master defining the HBase root and file system.
840   * @return A map for each table and its percentage (never null).
841   * @throws IOException When scanning the directory fails.
842   */
843  public static Map<String, Integer> getTableFragmentation(final HMaster master)
844    throws IOException {
845    Path path = CommonFSUtils.getRootDir(master.getConfiguration());
846    // since HMaster.getFileSystem() is package private
847    FileSystem fs = path.getFileSystem(master.getConfiguration());
848    return getTableFragmentation(fs, path);
849  }
850
851  /**
852   * Runs through the HBase rootdir and checks how many stores for each table have more than one
853   * file in them. Checks -ROOT- and hbase:meta too. The total percentage across all tables is
854   * stored under the special key "-TOTAL-".
855   * @param fs           The file system to use
856   * @param hbaseRootDir The root directory to scan
857   * @return A map for each table and its percentage (never null)
858   * @throws IOException When scanning the directory fails
859   */
860  public static Map<String, Integer> getTableFragmentation(final FileSystem fs,
861    final Path hbaseRootDir) throws IOException {
862    Map<String, Integer> frags = new HashMap<>();
863    int cfCountTotal = 0;
864    int cfFragTotal = 0;
865    PathFilter regionFilter = new RegionDirFilter(fs);
866    PathFilter familyFilter = new FamilyDirFilter(fs);
867    List<Path> tableDirs = getTableDirs(fs, hbaseRootDir);
868    for (Path d : tableDirs) {
869      int cfCount = 0;
870      int cfFrag = 0;
871      FileStatus[] regionDirs = fs.listStatus(d, regionFilter);
872      for (FileStatus regionDir : regionDirs) {
873        Path dd = regionDir.getPath();
874        // else its a region name, now look in region for families
875        FileStatus[] familyDirs = fs.listStatus(dd, familyFilter);
876        for (FileStatus familyDir : familyDirs) {
877          cfCount++;
878          cfCountTotal++;
879          Path family = familyDir.getPath();
880          // now in family make sure only one file
881          FileStatus[] familyStatus = fs.listStatus(family);
882          if (familyStatus.length > 1) {
883            cfFrag++;
884            cfFragTotal++;
885          }
886        }
887      }
888      // compute percentage per table and store in result list
889      frags.put(CommonFSUtils.getTableName(d).getNameAsString(),
890        cfCount == 0 ? 0 : Math.round((float) cfFrag / cfCount * 100));
891    }
892    // set overall percentage for all tables
893    frags.put("-TOTAL-",
894      cfCountTotal == 0 ? 0 : Math.round((float) cfFragTotal / cfCountTotal * 100));
895    return frags;
896  }
897
898  public static void renameFile(FileSystem fs, Path src, Path dst) throws IOException {
899    if (fs.exists(dst) && !fs.delete(dst, false)) {
900      throw new IOException("Can not delete " + dst);
901    }
902    if (!fs.rename(src, dst)) {
903      throw new IOException("Can not rename from " + src + " to " + dst);
904    }
905  }
906
907  /**
908   * A {@link PathFilter} that returns only regular files.
909   */
910  static class FileFilter extends AbstractFileStatusFilter {
911    private final FileSystem fs;
912
913    public FileFilter(final FileSystem fs) {
914      this.fs = fs;
915    }
916
917    @Override
918    protected boolean accept(Path p, @CheckForNull Boolean isDir) {
919      try {
920        return isFile(fs, isDir, p);
921      } catch (IOException e) {
922        LOG.warn("Unable to verify if path={} is a regular file", p, e);
923        return false;
924      }
925    }
926  }
927
928  /**
929   * Directory filter that doesn't include any of the directories in the specified blacklist
930   */
931  public static class BlackListDirFilter extends AbstractFileStatusFilter {
932    private final FileSystem fs;
933    private List<String> blacklist;
934
935    /**
936     * Create a filter on the givem filesystem with the specified blacklist
937     * @param fs                     filesystem to filter
938     * @param directoryNameBlackList list of the names of the directories to filter. If
939     *                               <tt>null</tt>, all directories are returned
940     */
941    @SuppressWarnings("unchecked")
942    public BlackListDirFilter(final FileSystem fs, final List<String> directoryNameBlackList) {
943      this.fs = fs;
944      blacklist = (List<String>) (directoryNameBlackList == null
945        ? Collections.emptyList()
946        : directoryNameBlackList);
947    }
948
949    @Override
950    protected boolean accept(Path p, @CheckForNull Boolean isDir) {
951      if (!isValidName(p.getName())) {
952        return false;
953      }
954
955      try {
956        return isDirectory(fs, isDir, p);
957      } catch (IOException e) {
958        LOG.warn("An error occurred while verifying if [{}] is a valid directory."
959          + " Returning 'not valid' and continuing.", p, e);
960        return false;
961      }
962    }
963
964    protected boolean isValidName(final String name) {
965      return !blacklist.contains(name);
966    }
967  }
968
969  /**
970   * A {@link PathFilter} that only allows directories.
971   */
972  public static class DirFilter extends BlackListDirFilter {
973
974    public DirFilter(FileSystem fs) {
975      super(fs, null);
976    }
977  }
978
979  /**
980   * A {@link PathFilter} that returns usertable directories. To get all directories use the
981   * {@link BlackListDirFilter} with a <tt>null</tt> blacklist
982   */
983  public static class UserTableDirFilter extends BlackListDirFilter {
984    public UserTableDirFilter(FileSystem fs) {
985      super(fs, HConstants.HBASE_NON_TABLE_DIRS);
986    }
987
988    @Override
989    protected boolean isValidName(final String name) {
990      if (!super.isValidName(name)) return false;
991
992      try {
993        TableName.isLegalTableQualifierName(Bytes.toBytes(name));
994      } catch (IllegalArgumentException e) {
995        LOG.info("Invalid table name: {}", name);
996        return false;
997      }
998      return true;
999    }
1000  }
1001
1002  public static List<Path> getTableDirs(final FileSystem fs, final Path rootdir)
1003    throws IOException {
1004    List<Path> tableDirs = new ArrayList<>();
1005    Path baseNamespaceDir = new Path(rootdir, HConstants.BASE_NAMESPACE_DIR);
1006    if (fs.exists(baseNamespaceDir)) {
1007      for (FileStatus status : fs.globStatus(new Path(baseNamespaceDir, "*"))) {
1008        tableDirs.addAll(FSUtils.getLocalTableDirs(fs, status.getPath()));
1009      }
1010    }
1011    return tableDirs;
1012  }
1013
1014  /**
1015   * @return All the table directories under <code>rootdir</code>. Ignore non table hbase folders
1016   *         such as .logs, .oldlogs, .corrupt folders.
1017   */
1018  public static List<Path> getLocalTableDirs(final FileSystem fs, final Path rootdir)
1019    throws IOException {
1020    // presumes any directory under hbase.rootdir is a table
1021    FileStatus[] dirs = fs.listStatus(rootdir, new UserTableDirFilter(fs));
1022    List<Path> tabledirs = new ArrayList<>(dirs.length);
1023    for (FileStatus dir : dirs) {
1024      tabledirs.add(dir.getPath());
1025    }
1026    return tabledirs;
1027  }
1028
1029  /**
1030   * A filter to exclude meta tables belonging to foreign clusters. This is essential in a
1031   * read-replica setup where multiple clusters share the same fs.
1032   * @param tablePath The Path to the table directory.
1033   * @return {@code true} if the path is a regular table or the cluster's own meta table.
1034   *         {@code false} if it is a meta table belonging to a different cluster.
1035   */
1036  public static boolean isLocalMetaTable(Path tablePath) {
1037    if (tablePath == null) {
1038      return false;
1039    }
1040    String dirName = tablePath.getName();
1041    if (dirName.startsWith(TableName.META_TABLE_NAME.getQualifierAsString())) {
1042      return TableName.valueOf(TableName.META_TABLE_NAME.getNamespaceAsString(), dirName)
1043        .equals(TableName.META_TABLE_NAME);
1044    }
1045    return true;
1046  }
1047
1048  /**
1049   * Filter for all dirs that don't start with '.'
1050   */
1051  public static class RegionDirFilter extends AbstractFileStatusFilter {
1052    // This pattern will accept 0.90+ style hex region dirs and older numeric region dir names.
1053    final public static Pattern regionDirPattern = Pattern.compile("^[0-9a-f]*$");
1054    final FileSystem fs;
1055
1056    public RegionDirFilter(FileSystem fs) {
1057      this.fs = fs;
1058    }
1059
1060    @Override
1061    protected boolean accept(Path p, @CheckForNull Boolean isDir) {
1062      if (!regionDirPattern.matcher(p.getName()).matches()) {
1063        return false;
1064      }
1065
1066      try {
1067        return isDirectory(fs, isDir, p);
1068      } catch (IOException ioe) {
1069        // Maybe the file was moved or the fs was disconnected.
1070        LOG.warn("Skipping file {} due to IOException", p, ioe);
1071        return false;
1072      }
1073    }
1074  }
1075
1076  /**
1077   * Given a particular table dir, return all the regiondirs inside it, excluding files such as
1078   * .tableinfo
1079   * @param fs       A file system for the Path
1080   * @param tableDir Path to a specific table directory &lt;hbase.rootdir&gt;/&lt;tabledir&gt;
1081   * @return List of paths to valid region directories in table dir.
1082   */
1083  public static List<Path> getRegionDirs(final FileSystem fs, final Path tableDir)
1084    throws IOException {
1085    // assumes we are in a table dir.
1086    List<FileStatus> rds = listStatusWithStatusFilter(fs, tableDir, new RegionDirFilter(fs));
1087    if (rds == null) {
1088      return Collections.emptyList();
1089    }
1090    List<Path> regionDirs = new ArrayList<>(rds.size());
1091    for (FileStatus rdfs : rds) {
1092      Path rdPath = rdfs.getPath();
1093      regionDirs.add(rdPath);
1094    }
1095    return regionDirs;
1096  }
1097
1098  public static Path getRegionDirFromRootDir(Path rootDir, RegionInfo region) {
1099    return getRegionDirFromTableDir(CommonFSUtils.getTableDir(rootDir, region.getTable()), region);
1100  }
1101
1102  public static Path getRegionDirFromTableDir(Path tableDir, RegionInfo region) {
1103    return getRegionDirFromTableDir(tableDir,
1104      ServerRegionReplicaUtil.getRegionInfoForFs(region).getEncodedName());
1105  }
1106
1107  public static Path getRegionDirFromTableDir(Path tableDir, String encodedRegionName) {
1108    return new Path(tableDir, encodedRegionName);
1109  }
1110
1111  /**
1112   * Filter for all dirs that are legal column family names. This is generally used for colfam dirs
1113   * &lt;hbase.rootdir&gt;/&lt;tabledir&gt;/&lt;regiondir&gt;/&lt;colfamdir&gt;.
1114   */
1115  public static class FamilyDirFilter extends AbstractFileStatusFilter {
1116    final FileSystem fs;
1117
1118    public FamilyDirFilter(FileSystem fs) {
1119      this.fs = fs;
1120    }
1121
1122    @Override
1123    protected boolean accept(Path p, @CheckForNull Boolean isDir) {
1124      try {
1125        // throws IAE if invalid
1126        ColumnFamilyDescriptorBuilder.isLegalColumnFamilyName(Bytes.toBytes(p.getName()));
1127      } catch (IllegalArgumentException iae) {
1128        // path name is an invalid family name and thus is excluded.
1129        return false;
1130      }
1131
1132      try {
1133        return isDirectory(fs, isDir, p);
1134      } catch (IOException ioe) {
1135        // Maybe the file was moved or the fs was disconnected.
1136        LOG.warn("Skipping file {} due to IOException", p, ioe);
1137        return false;
1138      }
1139    }
1140  }
1141
1142  /**
1143   * Given a particular region dir, return all the familydirs inside it
1144   * @param fs        A file system for the Path
1145   * @param regionDir Path to a specific region directory
1146   * @return List of paths to valid family directories in region dir.
1147   */
1148  public static List<Path> getFamilyDirs(final FileSystem fs, final Path regionDir)
1149    throws IOException {
1150    // assumes we are in a region dir.
1151    return getFilePaths(fs, regionDir, new FamilyDirFilter(fs));
1152  }
1153
1154  public static List<Path> getReferenceFilePaths(final FileSystem fs, final Path familyDir)
1155    throws IOException {
1156    return getFilePaths(fs, familyDir, new ReferenceFileFilter(fs));
1157  }
1158
1159  public static List<Path> getReferenceAndLinkFilePaths(final FileSystem fs, final Path familyDir)
1160    throws IOException {
1161    return getFilePaths(fs, familyDir, new ReferenceAndLinkFileFilter(fs));
1162  }
1163
1164  private static List<Path> getFilePaths(final FileSystem fs, final Path dir,
1165    final PathFilter pathFilter) throws IOException {
1166    FileStatus[] fds = fs.listStatus(dir, pathFilter);
1167    List<Path> files = new ArrayList<>(fds.length);
1168    for (FileStatus fdfs : fds) {
1169      Path fdPath = fdfs.getPath();
1170      files.add(fdPath);
1171    }
1172    return files;
1173  }
1174
1175  public static int getRegionReferenceAndLinkFileCount(final FileSystem fs, final Path p) {
1176    int result = 0;
1177    try {
1178      for (Path familyDir : getFamilyDirs(fs, p)) {
1179        result += getReferenceAndLinkFilePaths(fs, familyDir).size();
1180      }
1181    } catch (IOException e) {
1182      LOG.warn("Error Counting reference files.", e);
1183    }
1184    return result;
1185  }
1186
1187  public static class ReferenceAndLinkFileFilter implements PathFilter {
1188
1189    private final FileSystem fs;
1190
1191    public ReferenceAndLinkFileFilter(FileSystem fs) {
1192      this.fs = fs;
1193    }
1194
1195    @Override
1196    public boolean accept(Path rd) {
1197      try {
1198        // only files can be references.
1199        return !fs.getFileStatus(rd).isDirectory()
1200          && (StoreFileInfo.isReference(rd) || HFileLink.isHFileLink(rd));
1201      } catch (IOException ioe) {
1202        // Maybe the file was moved or the fs was disconnected.
1203        LOG.warn("Skipping file " + rd + " due to IOException", ioe);
1204        return false;
1205      }
1206    }
1207  }
1208
1209  /**
1210   * Filter for HFiles that excludes reference files.
1211   */
1212  public static class HFileFilter extends AbstractFileStatusFilter {
1213    final FileSystem fs;
1214
1215    public HFileFilter(FileSystem fs) {
1216      this.fs = fs;
1217    }
1218
1219    @Override
1220    protected boolean accept(Path p, @CheckForNull Boolean isDir) {
1221      if (!StoreFileInfo.isHFile(p) && !StoreFileInfo.isMobFile(p)) {
1222        return false;
1223      }
1224
1225      try {
1226        return isFile(fs, isDir, p);
1227      } catch (IOException ioe) {
1228        // Maybe the file was moved or the fs was disconnected.
1229        LOG.warn("Skipping file {} due to IOException", p, ioe);
1230        return false;
1231      }
1232    }
1233  }
1234
1235  /**
1236   * Filter for HFileLinks (StoreFiles and HFiles not included). the filter itself does not consider
1237   * if a link is file or not.
1238   */
1239  public static class HFileLinkFilter implements PathFilter {
1240
1241    @Override
1242    public boolean accept(Path p) {
1243      return HFileLink.isHFileLink(p);
1244    }
1245  }
1246
1247  public static class ReferenceFileFilter extends AbstractFileStatusFilter {
1248
1249    private final FileSystem fs;
1250
1251    public ReferenceFileFilter(FileSystem fs) {
1252      this.fs = fs;
1253    }
1254
1255    @Override
1256    protected boolean accept(Path p, @CheckForNull Boolean isDir) {
1257      if (!StoreFileInfo.isReference(p)) {
1258        return false;
1259      }
1260
1261      try {
1262        // only files can be references.
1263        return isFile(fs, isDir, p);
1264      } catch (IOException ioe) {
1265        // Maybe the file was moved or the fs was disconnected.
1266        LOG.warn("Skipping file {} due to IOException", p, ioe);
1267        return false;
1268      }
1269    }
1270  }
1271
1272  /**
1273   * Called every so-often by storefile map builder getTableStoreFilePathMap to report progress.
1274   */
1275  interface ProgressReporter {
1276    /**
1277     * @param status File or directory we are about to process.
1278     */
1279    void progress(FileStatus status);
1280  }
1281
1282  /**
1283   * Runs through the HBase rootdir/tablename and creates a reverse lookup map for table StoreFile
1284   * names to the full Path. <br>
1285   * Example...<br>
1286   * Key = 3944417774205889744 <br>
1287   * Value = hdfs://localhost:51169/user/userid/-ROOT-/70236052/info/3944417774205889744
1288   * @param map          map to add values. If null, this method will create and populate one to
1289   *                     return
1290   * @param fs           The file system to use.
1291   * @param hbaseRootDir The root directory to scan.
1292   * @param tableName    name of the table to scan.
1293   * @return Map keyed by StoreFile name with a value of the full Path.
1294   * @throws IOException When scanning the directory fails.
1295   */
1296  public static Map<String, Path> getTableStoreFilePathMap(Map<String, Path> map,
1297    final FileSystem fs, final Path hbaseRootDir, TableName tableName)
1298    throws IOException, InterruptedException {
1299    return getTableStoreFilePathMap(map, fs, hbaseRootDir, tableName, null, null,
1300      (ProgressReporter) null);
1301  }
1302
1303  /**
1304   * Runs through the HBase rootdir/tablename and creates a reverse lookup map for table StoreFile
1305   * names to the full Path. Note that because this method can be called on a 'live' HBase system
1306   * that we will skip files that no longer exist by the time we traverse them and similarly the
1307   * user of the result needs to consider that some entries in this map may not exist by the time
1308   * this call completes. <br>
1309   * Example...<br>
1310   * Key = 3944417774205889744 <br>
1311   * Value = hdfs://localhost:51169/user/userid/-ROOT-/70236052/info/3944417774205889744
1312   * @param resultMap        map to add values. If null, this method will create and populate one to
1313   *                         return
1314   * @param fs               The file system to use.
1315   * @param hbaseRootDir     The root directory to scan.
1316   * @param tableName        name of the table to scan.
1317   * @param sfFilter         optional path filter to apply to store files
1318   * @param executor         optional executor service to parallelize this operation
1319   * @param progressReporter Instance or null; gets called every time we move to new region of
1320   *                         family dir and for each store file.
1321   * @return Map keyed by StoreFile name with a value of the full Path.
1322   * @throws IOException When scanning the directory fails.
1323   * @deprecated Since 2.3.0. For removal in hbase4. Use ProgressReporter override instead.
1324   */
1325  @Deprecated
1326  public static Map<String, Path> getTableStoreFilePathMap(Map<String, Path> resultMap,
1327    final FileSystem fs, final Path hbaseRootDir, TableName tableName, final PathFilter sfFilter,
1328    ExecutorService executor, final HbckErrorReporter progressReporter)
1329    throws IOException, InterruptedException {
1330    return getTableStoreFilePathMap(resultMap, fs, hbaseRootDir, tableName, sfFilter, executor,
1331      new ProgressReporter() {
1332        @Override
1333        public void progress(FileStatus status) {
1334          // status is not used in this implementation.
1335          progressReporter.progress();
1336        }
1337      });
1338  }
1339
1340  /**
1341   * Runs through the HBase rootdir/tablename and creates a reverse lookup map for table StoreFile
1342   * names to the full Path. Note that because this method can be called on a 'live' HBase system
1343   * that we will skip files that no longer exist by the time we traverse them and similarly the
1344   * user of the result needs to consider that some entries in this map may not exist by the time
1345   * this call completes. <br>
1346   * Example...<br>
1347   * Key = 3944417774205889744 <br>
1348   * Value = hdfs://localhost:51169/user/userid/-ROOT-/70236052/info/3944417774205889744
1349   * @param resultMap        map to add values. If null, this method will create and populate one to
1350   *                         return
1351   * @param fs               The file system to use.
1352   * @param hbaseRootDir     The root directory to scan.
1353   * @param tableName        name of the table to scan.
1354   * @param sfFilter         optional path filter to apply to store files
1355   * @param executor         optional executor service to parallelize this operation
1356   * @param progressReporter Instance or null; gets called every time we move to new region of
1357   *                         family dir and for each store file.
1358   * @return Map keyed by StoreFile name with a value of the full Path.
1359   * @throws IOException          When scanning the directory fails.
1360   * @throws InterruptedException the thread is interrupted, either before or during the activity.
1361   */
1362  public static Map<String, Path> getTableStoreFilePathMap(Map<String, Path> resultMap,
1363    final FileSystem fs, final Path hbaseRootDir, TableName tableName, final PathFilter sfFilter,
1364    ExecutorService executor, final ProgressReporter progressReporter)
1365    throws IOException, InterruptedException {
1366
1367    final Map<String, Path> finalResultMap =
1368      resultMap == null ? new ConcurrentHashMap<>(128, 0.75f, 32) : resultMap;
1369
1370    // only include the directory paths to tables
1371    Path tableDir = CommonFSUtils.getTableDir(hbaseRootDir, tableName);
1372    // Inside a table, there are compaction.dir directories to skip. Otherwise, all else
1373    // should be regions.
1374    final FamilyDirFilter familyFilter = new FamilyDirFilter(fs);
1375    final Vector<Exception> exceptions = new Vector<>();
1376
1377    try {
1378      List<FileStatus> regionDirs =
1379        FSUtils.listStatusWithStatusFilter(fs, tableDir, new RegionDirFilter(fs));
1380      if (regionDirs == null) {
1381        return finalResultMap;
1382      }
1383
1384      final List<Future<?>> futures = new ArrayList<>(regionDirs.size());
1385
1386      for (FileStatus regionDir : regionDirs) {
1387        if (null != progressReporter) {
1388          progressReporter.progress(regionDir);
1389        }
1390        final Path dd = regionDir.getPath();
1391
1392        if (!exceptions.isEmpty()) {
1393          break;
1394        }
1395
1396        Runnable getRegionStoreFileMapCall = new Runnable() {
1397          @Override
1398          public void run() {
1399            try {
1400              HashMap<String, Path> regionStoreFileMap = new HashMap<>();
1401              List<FileStatus> familyDirs =
1402                FSUtils.listStatusWithStatusFilter(fs, dd, familyFilter);
1403              if (familyDirs == null) {
1404                if (!fs.exists(dd)) {
1405                  LOG.warn("Skipping region because it no longer exists: " + dd);
1406                } else {
1407                  LOG.warn("Skipping region because it has no family dirs: " + dd);
1408                }
1409                return;
1410              }
1411              for (FileStatus familyDir : familyDirs) {
1412                if (null != progressReporter) {
1413                  progressReporter.progress(familyDir);
1414                }
1415                Path family = familyDir.getPath();
1416                if (family.getName().equals(HConstants.RECOVERED_EDITS_DIR)) {
1417                  continue;
1418                }
1419                // now in family, iterate over the StoreFiles and
1420                // put in map
1421                FileStatus[] familyStatus = fs.listStatus(family);
1422                for (FileStatus sfStatus : familyStatus) {
1423                  if (null != progressReporter) {
1424                    progressReporter.progress(sfStatus);
1425                  }
1426                  Path sf = sfStatus.getPath();
1427                  if (sfFilter == null || sfFilter.accept(sf)) {
1428                    regionStoreFileMap.put(sf.getName(), sf);
1429                  }
1430                }
1431              }
1432              finalResultMap.putAll(regionStoreFileMap);
1433            } catch (Exception e) {
1434              LOG.error("Could not get region store file map for region: " + dd, e);
1435              exceptions.add(e);
1436            }
1437          }
1438        };
1439
1440        // If executor is available, submit async tasks to exec concurrently, otherwise
1441        // just do serial sync execution
1442        if (executor != null) {
1443          Future<?> future = executor.submit(getRegionStoreFileMapCall);
1444          futures.add(future);
1445        } else {
1446          FutureTask<?> future = new FutureTask<>(getRegionStoreFileMapCall, null);
1447          future.run();
1448          futures.add(future);
1449        }
1450      }
1451
1452      // Ensure all pending tasks are complete (or that we run into an exception)
1453      for (Future<?> f : futures) {
1454        if (!exceptions.isEmpty()) {
1455          break;
1456        }
1457        try {
1458          f.get();
1459        } catch (ExecutionException e) {
1460          LOG.error("Unexpected exec exception!  Should've been caught already.  (Bug?)", e);
1461          // Shouldn't happen, we already logged/caught any exceptions in the Runnable
1462        }
1463      }
1464    } catch (IOException e) {
1465      LOG.error("Cannot execute getTableStoreFilePathMap for " + tableName, e);
1466      exceptions.add(e);
1467    } finally {
1468      if (!exceptions.isEmpty()) {
1469        // Just throw the first exception as an indication something bad happened
1470        // Don't need to propagate all the exceptions, we already logged them all anyway
1471        Throwables.propagateIfPossible(exceptions.firstElement(), IOException.class);
1472        throw new IOException(exceptions.firstElement());
1473      }
1474    }
1475
1476    return finalResultMap;
1477  }
1478
1479  public static int getRegionReferenceFileCount(final FileSystem fs, final Path p) {
1480    int result = 0;
1481    try {
1482      for (Path familyDir : getFamilyDirs(fs, p)) {
1483        result += getReferenceFilePaths(fs, familyDir).size();
1484      }
1485    } catch (IOException e) {
1486      LOG.warn("Error counting reference files", e);
1487    }
1488    return result;
1489  }
1490
1491  /**
1492   * Runs through the HBase rootdir and creates a reverse lookup map for table StoreFile names to
1493   * the full Path. <br>
1494   * Example...<br>
1495   * Key = 3944417774205889744 <br>
1496   * Value = hdfs://localhost:51169/user/userid/-ROOT-/70236052/info/3944417774205889744
1497   * @param fs           The file system to use.
1498   * @param hbaseRootDir The root directory to scan.
1499   * @return Map keyed by StoreFile name with a value of the full Path.
1500   * @throws IOException When scanning the directory fails.
1501   */
1502  public static Map<String, Path> getTableStoreFilePathMap(final FileSystem fs,
1503    final Path hbaseRootDir) throws IOException, InterruptedException {
1504    return getTableStoreFilePathMap(fs, hbaseRootDir, null, null, (ProgressReporter) null);
1505  }
1506
1507  /**
1508   * Runs through the HBase rootdir and creates a reverse lookup map for table StoreFile names to
1509   * the full Path. <br>
1510   * Example...<br>
1511   * Key = 3944417774205889744 <br>
1512   * Value = hdfs://localhost:51169/user/userid/-ROOT-/70236052/info/3944417774205889744
1513   * @param fs               The file system to use.
1514   * @param hbaseRootDir     The root directory to scan.
1515   * @param sfFilter         optional path filter to apply to store files
1516   * @param executor         optional executor service to parallelize this operation
1517   * @param progressReporter Instance or null; gets called every time we move to new region of
1518   *                         family dir and for each store file.
1519   * @return Map keyed by StoreFile name with a value of the full Path.
1520   * @throws IOException When scanning the directory fails.
1521   * @deprecated Since 2.3.0. Will be removed in hbase4. Used
1522   *             {@link #getTableStoreFilePathMap(FileSystem, Path, PathFilter, ExecutorService, ProgressReporter)}
1523   */
1524  @Deprecated
1525  public static Map<String, Path> getTableStoreFilePathMap(final FileSystem fs,
1526    final Path hbaseRootDir, PathFilter sfFilter, ExecutorService executor,
1527    HbckErrorReporter progressReporter) throws IOException, InterruptedException {
1528    return getTableStoreFilePathMap(fs, hbaseRootDir, sfFilter, executor, new ProgressReporter() {
1529      @Override
1530      public void progress(FileStatus status) {
1531        // status is not used in this implementation.
1532        progressReporter.progress();
1533      }
1534    });
1535  }
1536
1537  /**
1538   * Runs through the HBase rootdir and creates a reverse lookup map for table StoreFile names to
1539   * the full Path. <br>
1540   * Example...<br>
1541   * Key = 3944417774205889744 <br>
1542   * Value = hdfs://localhost:51169/user/userid/-ROOT-/70236052/info/3944417774205889744
1543   * @param fs               The file system to use.
1544   * @param hbaseRootDir     The root directory to scan.
1545   * @param sfFilter         optional path filter to apply to store files
1546   * @param executor         optional executor service to parallelize this operation
1547   * @param progressReporter Instance or null; gets called every time we move to new region of
1548   *                         family dir and for each store file.
1549   * @return Map keyed by StoreFile name with a value of the full Path.
1550   * @throws IOException When scanning the directory fails.
1551   */
1552  public static Map<String, Path> getTableStoreFilePathMap(final FileSystem fs,
1553    final Path hbaseRootDir, PathFilter sfFilter, ExecutorService executor,
1554    ProgressReporter progressReporter) throws IOException, InterruptedException {
1555    ConcurrentHashMap<String, Path> map = new ConcurrentHashMap<>(1024, 0.75f, 32);
1556
1557    // if this method looks similar to 'getTableFragmentation' that is because
1558    // it was borrowed from it.
1559
1560    // only include the directory paths to tables
1561    for (Path tableDir : FSUtils.getTableDirs(fs, hbaseRootDir)) {
1562      getTableStoreFilePathMap(map, fs, hbaseRootDir, CommonFSUtils.getTableName(tableDir),
1563        sfFilter, executor, progressReporter);
1564    }
1565    return map;
1566  }
1567
1568  /**
1569   * Filters FileStatuses in an array and returns a list
1570   * @param input  An array of FileStatuses
1571   * @param filter A required filter to filter the array
1572   * @return A list of FileStatuses
1573   */
1574  public static List<FileStatus> filterFileStatuses(FileStatus[] input, FileStatusFilter filter) {
1575    if (input == null) return null;
1576    return filterFileStatuses(Iterators.forArray(input), filter);
1577  }
1578
1579  /**
1580   * Filters FileStatuses in an iterator and returns a list
1581   * @param input  An iterator of FileStatuses
1582   * @param filter A required filter to filter the array
1583   * @return A list of FileStatuses
1584   */
1585  public static List<FileStatus> filterFileStatuses(Iterator<FileStatus> input,
1586    FileStatusFilter filter) {
1587    if (input == null) return null;
1588    ArrayList<FileStatus> results = new ArrayList<>();
1589    while (input.hasNext()) {
1590      FileStatus f = input.next();
1591      if (filter.accept(f)) {
1592        results.add(f);
1593      }
1594    }
1595    return results;
1596  }
1597
1598  /**
1599   * Calls fs.listStatus() and treats FileNotFoundException as non-fatal This accommodates
1600   * differences between hadoop versions, where hadoop 1 does not throw a FileNotFoundException, and
1601   * return an empty FileStatus[] while Hadoop 2 will throw FileNotFoundException.
1602   * @param fs     file system
1603   * @param dir    directory
1604   * @param filter file status filter
1605   * @return null if dir is empty or doesn't exist, otherwise FileStatus list
1606   */
1607  public static List<FileStatus> listStatusWithStatusFilter(final FileSystem fs, final Path dir,
1608    final FileStatusFilter filter) throws IOException {
1609    FileStatus[] status = null;
1610    try {
1611      status = fs.listStatus(dir);
1612    } catch (FileNotFoundException fnfe) {
1613      LOG.trace("{} does not exist", dir);
1614      return null;
1615    }
1616
1617    if (ArrayUtils.getLength(status) == 0) {
1618      return null;
1619    }
1620
1621    if (filter == null) {
1622      return Arrays.asList(status);
1623    } else {
1624      List<FileStatus> status2 = filterFileStatuses(status, filter);
1625      if (status2 == null || status2.isEmpty()) {
1626        return null;
1627      } else {
1628        return status2;
1629      }
1630    }
1631  }
1632
1633  /**
1634   * This function is to scan the root path of the file system to get the degree of locality for
1635   * each region on each of the servers having at least one block of that region. This is used by
1636   * the tool {@link org.apache.hadoop.hbase.master.RegionPlacementMaintainer} the configuration to
1637   * use
1638   * @return the mapping from region encoded name to a map of server names to locality fraction in
1639   *         case of file system errors or interrupts
1640   */
1641  public static Map<String, Map<String, Float>>
1642    getRegionDegreeLocalityMappingFromFS(final Configuration conf) throws IOException {
1643    return getRegionDegreeLocalityMappingFromFS(conf, null,
1644      conf.getInt(THREAD_POOLSIZE, DEFAULT_THREAD_POOLSIZE));
1645
1646  }
1647
1648  /**
1649   * This function is to scan the root path of the file system to get the degree of locality for
1650   * each region on each of the servers having at least one block of that region. the configuration
1651   * to use the table you wish to scan locality for the thread pool size to use
1652   * @return the mapping from region encoded name to a map of server names to locality fraction in
1653   *         case of file system errors or interrupts
1654   */
1655  public static Map<String, Map<String, Float>> getRegionDegreeLocalityMappingFromFS(
1656    final Configuration conf, final String desiredTable, int threadPoolSize) throws IOException {
1657    Map<String, Map<String, Float>> regionDegreeLocalityMapping = new ConcurrentHashMap<>();
1658    getRegionLocalityMappingFromFS(conf, desiredTable, threadPoolSize, regionDegreeLocalityMapping);
1659    return regionDegreeLocalityMapping;
1660  }
1661
1662  /**
1663   * This function is to scan the root path of the file system to get either the mapping between the
1664   * region name and its best locality region server or the degree of locality of each region on
1665   * each of the servers having at least one block of that region. The output map parameters are
1666   * both optional. the configuration to use the table you wish to scan locality for the thread pool
1667   * size to use the map into which to put the locality degree mapping or null, must be a
1668   * thread-safe implementation in case of file system errors or interrupts
1669   */
1670  private static void getRegionLocalityMappingFromFS(final Configuration conf,
1671    final String desiredTable, int threadPoolSize,
1672    final Map<String, Map<String, Float>> regionDegreeLocalityMapping) throws IOException {
1673    final FileSystem fs = FileSystem.get(conf);
1674    final Path rootPath = CommonFSUtils.getRootDir(conf);
1675    final long startTime = EnvironmentEdgeManager.currentTime();
1676    final Path queryPath;
1677    // The table files are in ${hbase.rootdir}/data/<namespace>/<table>/*
1678    if (null == desiredTable) {
1679      queryPath =
1680        new Path(new Path(rootPath, HConstants.BASE_NAMESPACE_DIR).toString() + "/*/*/*/");
1681    } else {
1682      queryPath = new Path(
1683        CommonFSUtils.getTableDir(rootPath, TableName.valueOf(desiredTable)).toString() + "/*/");
1684    }
1685
1686    // reject all paths that are not appropriate
1687    PathFilter pathFilter = new PathFilter() {
1688      @Override
1689      public boolean accept(Path path) {
1690        // this is the region name; it may get some noise data
1691        if (null == path) {
1692          return false;
1693        }
1694
1695        // no parent?
1696        Path parent = path.getParent();
1697        if (null == parent) {
1698          return false;
1699        }
1700
1701        String regionName = path.getName();
1702        if (null == regionName) {
1703          return false;
1704        }
1705
1706        if (!regionName.toLowerCase(Locale.ROOT).matches("[0-9a-f]+")) {
1707          return false;
1708        }
1709        return true;
1710      }
1711    };
1712
1713    FileStatus[] statusList = fs.globStatus(queryPath, pathFilter);
1714
1715    if (LOG.isDebugEnabled()) {
1716      LOG.debug("Query Path: {} ; # list of files: {}", queryPath, Arrays.toString(statusList));
1717    }
1718
1719    if (null == statusList) {
1720      return;
1721    }
1722
1723    // lower the number of threads in case we have very few expected regions
1724    threadPoolSize = Math.min(threadPoolSize, statusList.length);
1725
1726    // run in multiple threads
1727    final ExecutorService tpe = Executors.newFixedThreadPool(threadPoolSize,
1728      new ThreadFactoryBuilder().setNameFormat("FSRegionQuery-pool-%d").setDaemon(true)
1729        .setUncaughtExceptionHandler(Threads.LOGGING_EXCEPTION_HANDLER).build());
1730    try {
1731      // ignore all file status items that are not of interest
1732      for (FileStatus regionStatus : statusList) {
1733        if (null == regionStatus || !regionStatus.isDirectory()) {
1734          continue;
1735        }
1736
1737        final Path regionPath = regionStatus.getPath();
1738        if (null != regionPath) {
1739          tpe.execute(new FSRegionScanner(fs, regionPath, null, regionDegreeLocalityMapping));
1740        }
1741      }
1742    } finally {
1743      tpe.shutdown();
1744      final long threadWakeFrequency = (long) conf.getInt(HConstants.THREAD_WAKE_FREQUENCY,
1745        HConstants.DEFAULT_THREAD_WAKE_FREQUENCY);
1746      try {
1747        // here we wait until TPE terminates, which is either naturally or by
1748        // exceptions in the execution of the threads
1749        while (!tpe.awaitTermination(threadWakeFrequency, TimeUnit.MILLISECONDS)) {
1750          // printing out rough estimate, so as to not introduce
1751          // AtomicInteger
1752          LOG.info("Locality checking is underway: { Scanned Regions : "
1753            + ((ThreadPoolExecutor) tpe).getCompletedTaskCount() + "/"
1754            + ((ThreadPoolExecutor) tpe).getTaskCount() + " }");
1755        }
1756      } catch (InterruptedException e) {
1757        Thread.currentThread().interrupt();
1758        throw (InterruptedIOException) new InterruptedIOException().initCause(e);
1759      }
1760    }
1761
1762    long overhead = EnvironmentEdgeManager.currentTime() - startTime;
1763    LOG.info("Scan DFS for locality info takes {}ms", overhead);
1764  }
1765
1766  /**
1767   * Do our short circuit read setup. Checks buffer size to use and whether to do checksumming in
1768   * hbase or hdfs.
1769   */
1770  public static void setupShortCircuitRead(final Configuration conf) {
1771    // Check that the user has not set the "dfs.client.read.shortcircuit.skip.checksum" property.
1772    boolean shortCircuitSkipChecksum =
1773      conf.getBoolean("dfs.client.read.shortcircuit.skip.checksum", false);
1774    boolean useHBaseChecksum = conf.getBoolean(HConstants.HBASE_CHECKSUM_VERIFICATION, true);
1775    if (shortCircuitSkipChecksum) {
1776      LOG.warn("Configuration \"dfs.client.read.shortcircuit.skip.checksum\" should not "
1777        + "be set to true."
1778        + (useHBaseChecksum
1779          ? " HBase checksum doesn't require "
1780            + "it, see https://issues.apache.org/jira/browse/HBASE-6868."
1781          : ""));
1782      assert !shortCircuitSkipChecksum; // this will fail if assertions are on
1783    }
1784    checkShortCircuitReadBufferSize(conf);
1785  }
1786
1787  /**
1788   * Check if short circuit read buffer size is set and if not, set it to hbase value.
1789   */
1790  public static void checkShortCircuitReadBufferSize(final Configuration conf) {
1791    final int defaultSize = HConstants.DEFAULT_BLOCKSIZE * 2;
1792    final int notSet = -1;
1793    // DFSConfigKeys.DFS_CLIENT_READ_SHORTCIRCUIT_BUFFER_SIZE_KEY is only defined in h2
1794    final String dfsKey = "dfs.client.read.shortcircuit.buffer.size";
1795    int size = conf.getInt(dfsKey, notSet);
1796    // If a size is set, return -- we will use it.
1797    if (size != notSet) return;
1798    // But short circuit buffer size is normally not set. Put in place the hbase wanted size.
1799    int hbaseSize = conf.getInt("hbase." + dfsKey, defaultSize);
1800    conf.setIfUnset(dfsKey, Integer.toString(hbaseSize));
1801  }
1802
1803  /**
1804   * Returns The DFSClient DFSHedgedReadMetrics instance or null if can't be found or not on hdfs.
1805   */
1806  public static DFSHedgedReadMetrics getDFSHedgedReadMetrics(final Configuration c)
1807    throws IOException {
1808    if (!CommonFSUtils.isHDFS(c)) {
1809      return null;
1810    }
1811    // getHedgedReadMetrics is package private. Get the DFSClient instance that is internal
1812    // to the DFS FS instance and make the method getHedgedReadMetrics accessible, then invoke it
1813    // to get the singleton instance of DFSHedgedReadMetrics shared by DFSClients.
1814    final String name = "getHedgedReadMetrics";
1815    DFSClient dfsclient = ((DistributedFileSystem) FileSystem.get(c)).getClient();
1816    Method m;
1817    try {
1818      m = dfsclient.getClass().getDeclaredMethod(name);
1819    } catch (NoSuchMethodException e) {
1820      LOG.warn(
1821        "Failed find method " + name + " in dfsclient; no hedged read metrics: " + e.getMessage());
1822      return null;
1823    } catch (SecurityException e) {
1824      LOG.warn(
1825        "Failed find method " + name + " in dfsclient; no hedged read metrics: " + e.getMessage());
1826      return null;
1827    }
1828    m.setAccessible(true);
1829    try {
1830      return (DFSHedgedReadMetrics) m.invoke(dfsclient);
1831    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
1832      LOG.warn("Failed invoking method " + name + " on dfsclient; no hedged read metrics: "
1833        + e.getMessage());
1834      return null;
1835    }
1836  }
1837
1838  public static List<Path> copyFilesParallel(FileSystem srcFS, Path src, FileSystem dstFS, Path dst,
1839    Configuration conf, int threads) throws IOException {
1840    ExecutorService pool = Executors.newFixedThreadPool(threads);
1841    List<Future<Void>> futures = new ArrayList<>();
1842    List<Path> traversedPaths;
1843    try {
1844      traversedPaths = copyFiles(srcFS, src, dstFS, dst, conf, pool, futures);
1845      for (Future<Void> future : futures) {
1846        future.get();
1847      }
1848    } catch (ExecutionException | InterruptedException | IOException e) {
1849      throw new IOException("Copy snapshot reference files failed", e);
1850    } finally {
1851      pool.shutdownNow();
1852    }
1853    return traversedPaths;
1854  }
1855
1856  private static List<Path> copyFiles(FileSystem srcFS, Path src, FileSystem dstFS, Path dst,
1857    Configuration conf, ExecutorService pool, List<Future<Void>> futures) throws IOException {
1858    List<Path> traversedPaths = new ArrayList<>();
1859    traversedPaths.add(dst);
1860    FileStatus currentFileStatus = srcFS.getFileStatus(src);
1861    if (currentFileStatus.isDirectory()) {
1862      if (!dstFS.mkdirs(dst)) {
1863        throw new IOException("Create directory failed: " + dst);
1864      }
1865      FileStatus[] subPaths = srcFS.listStatus(src);
1866      for (FileStatus subPath : subPaths) {
1867        traversedPaths.addAll(copyFiles(srcFS, subPath.getPath(), dstFS,
1868          new Path(dst, subPath.getPath().getName()), conf, pool, futures));
1869      }
1870    } else {
1871      Future<Void> future = pool.submit(() -> {
1872        FileUtil.copy(srcFS, src, dstFS, dst, false, false, conf);
1873        return null;
1874      });
1875      futures.add(future);
1876    }
1877    return traversedPaths;
1878  }
1879
1880  /** Returns A set containing all namenode addresses of fs */
1881  private static Set<InetSocketAddress> getNNAddresses(DistributedFileSystem fs,
1882    Configuration conf) {
1883    Set<InetSocketAddress> addresses = new HashSet<>();
1884    String serviceName = fs.getCanonicalServiceName();
1885
1886    if (serviceName.startsWith("ha-hdfs")) {
1887      try {
1888        Map<String, Map<String, InetSocketAddress>> addressMap =
1889          DFSUtil.getNNServiceRpcAddressesForCluster(conf);
1890        String nameService = serviceName.substring(serviceName.indexOf(":") + 1);
1891        if (addressMap.containsKey(nameService)) {
1892          Map<String, InetSocketAddress> nnMap = addressMap.get(nameService);
1893          for (Map.Entry<String, InetSocketAddress> e2 : nnMap.entrySet()) {
1894            InetSocketAddress addr = e2.getValue();
1895            addresses.add(addr);
1896          }
1897        }
1898      } catch (Exception e) {
1899        LOG.warn("DFSUtil.getNNServiceRpcAddresses failed. serviceName=" + serviceName, e);
1900      }
1901    } else {
1902      URI uri = fs.getUri();
1903      int port = uri.getPort();
1904      if (port < 0) {
1905        int idx = serviceName.indexOf(':');
1906        port = Integer.parseInt(serviceName.substring(idx + 1));
1907      }
1908      InetSocketAddress addr = new InetSocketAddress(uri.getHost(), port);
1909      addresses.add(addr);
1910    }
1911
1912    return addresses;
1913  }
1914
1915  /**
1916   * @param conf the Configuration of HBase
1917   * @return Whether srcFs and desFs are on same hdfs or not
1918   */
1919  public static boolean isSameHdfs(Configuration conf, FileSystem srcFs, FileSystem desFs) {
1920    // By getCanonicalServiceName, we could make sure both srcFs and desFs
1921    // show a unified format which contains scheme, host and port.
1922    String srcServiceName = srcFs.getCanonicalServiceName();
1923    String desServiceName = desFs.getCanonicalServiceName();
1924
1925    if (srcServiceName == null || desServiceName == null) {
1926      return false;
1927    }
1928    if (srcServiceName.equals(desServiceName)) {
1929      return true;
1930    }
1931    if (srcServiceName.startsWith("ha-hdfs") && desServiceName.startsWith("ha-hdfs")) {
1932      Collection<String> internalNameServices =
1933        conf.getTrimmedStringCollection("dfs.internal.nameservices");
1934      if (!internalNameServices.isEmpty()) {
1935        if (internalNameServices.contains(srcServiceName.split(":")[1])) {
1936          return true;
1937        } else {
1938          return false;
1939        }
1940      }
1941    }
1942    if (srcFs instanceof DistributedFileSystem && desFs instanceof DistributedFileSystem) {
1943      // If one serviceName is an HA format while the other is a non-HA format,
1944      // maybe they refer to the same FileSystem.
1945      // For example, srcFs is "ha-hdfs://nameservices" and desFs is "hdfs://activeNamenode:port"
1946      Set<InetSocketAddress> srcAddrs = getNNAddresses((DistributedFileSystem) srcFs, conf);
1947      Set<InetSocketAddress> desAddrs = getNNAddresses((DistributedFileSystem) desFs, conf);
1948      if (Sets.intersection(srcAddrs, desAddrs).size() > 0) {
1949        return true;
1950      }
1951    }
1952
1953    return false;
1954  }
1955}