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 */
018
019package org.apache.hadoop.hdfs;
020
021import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_ADMIN;
022import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_HTTPS_NEED_AUTH_DEFAULT;
023import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_HTTPS_NEED_AUTH_KEY;
024import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_HA_NAMENODES_KEY_PREFIX;
025import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_HA_NAMENODE_ID_KEY;
026import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_BACKUP_ADDRESS_KEY;
027import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_DEFAULT;
028import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY;
029import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_DEFAULT;
030import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_KEY;
031import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY;
032import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SECONDARY_HTTP_ADDRESS_KEY;
033import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SERVICE_RPC_ADDRESS_KEY;
034import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMESERVICES;
035import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMESERVICE_ID;
036
037import java.io.IOException;
038import java.io.PrintStream;
039import java.io.UnsupportedEncodingException;
040import java.net.InetAddress;
041import java.net.InetSocketAddress;
042import java.net.URI;
043import java.net.URISyntaxException;
044import java.security.SecureRandom;
045import java.text.SimpleDateFormat;
046import java.util.Arrays;
047import java.util.Collection;
048import java.util.Collections;
049import java.util.Comparator;
050import java.util.Date;
051import java.util.HashSet;
052import java.util.List;
053import java.util.Locale;
054import java.util.Map;
055import java.util.Random;
056import java.util.Set;
057
058import javax.net.SocketFactory;
059
060import org.apache.commons.cli.CommandLine;
061import org.apache.commons.cli.CommandLineParser;
062import org.apache.commons.cli.Option;
063import org.apache.commons.cli.Options;
064import org.apache.commons.cli.ParseException;
065import org.apache.commons.cli.PosixParser;
066import org.apache.commons.logging.Log;
067import org.apache.commons.logging.LogFactory;
068import org.apache.hadoop.HadoopIllegalArgumentException;
069import org.apache.hadoop.classification.InterfaceAudience;
070import org.apache.hadoop.conf.Configuration;
071import org.apache.hadoop.fs.BlockLocation;
072import org.apache.hadoop.fs.CommonConfigurationKeys;
073import org.apache.hadoop.fs.FileSystem;
074import org.apache.hadoop.fs.Path;
075import org.apache.hadoop.hdfs.protocol.ClientDatanodeProtocol;
076import org.apache.hadoop.hdfs.protocol.DatanodeID;
077import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
078import org.apache.hadoop.hdfs.protocol.HdfsConstants;
079import org.apache.hadoop.hdfs.protocol.LocatedBlock;
080import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
081import org.apache.hadoop.hdfs.protocolPB.ClientDatanodeProtocolTranslatorPB;
082import org.apache.hadoop.hdfs.server.namenode.FSDirectory;
083import org.apache.hadoop.hdfs.server.namenode.NameNode;
084import org.apache.hadoop.hdfs.web.SWebHdfsFileSystem;
085import org.apache.hadoop.hdfs.web.WebHdfsFileSystem;
086import org.apache.hadoop.http.HttpConfig;
087import org.apache.hadoop.http.HttpServer2;
088import org.apache.hadoop.ipc.ProtobufRpcEngine;
089import org.apache.hadoop.ipc.RPC;
090import org.apache.hadoop.net.NetUtils;
091import org.apache.hadoop.net.NodeBase;
092import org.apache.hadoop.security.SecurityUtil;
093import org.apache.hadoop.security.UserGroupInformation;
094import org.apache.hadoop.security.authorize.AccessControlList;
095import org.apache.hadoop.util.StringUtils;
096import org.apache.hadoop.util.ToolRunner;
097
098import com.google.common.annotations.VisibleForTesting;
099import com.google.common.base.Charsets;
100import com.google.common.base.Joiner;
101import com.google.common.base.Preconditions;
102import com.google.common.collect.Lists;
103import com.google.common.collect.Maps;
104import com.google.common.primitives.SignedBytes;
105import com.google.protobuf.BlockingService;
106
107@InterfaceAudience.Private
108public class DFSUtil {
109  public static final Log LOG = LogFactory.getLog(DFSUtil.class.getName());
110  
111  public static final byte[] EMPTY_BYTES = {};
112
113  /** Compare two byte arrays by lexicographical order. */
114  public static int compareBytes(byte[] left, byte[] right) {
115    if (left == null) {
116      left = EMPTY_BYTES;
117    }
118    if (right == null) {
119      right = EMPTY_BYTES;
120    }
121    return SignedBytes.lexicographicalComparator().compare(left, right);
122  }
123
124  private DFSUtil() { /* Hidden constructor */ }
125  private static final ThreadLocal<Random> RANDOM = new ThreadLocal<Random>() {
126    @Override
127    protected Random initialValue() {
128      return new Random();
129    }
130  };
131  
132  private static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {
133    @Override
134    protected SecureRandom initialValue() {
135      return new SecureRandom();
136    }
137  };
138
139  /** @return a pseudo random number generator. */
140  public static Random getRandom() {
141    return RANDOM.get();
142  }
143  
144  /** @return a pseudo secure random number generator. */
145  public static SecureRandom getSecureRandom() {
146    return SECURE_RANDOM.get();
147  }
148
149  /** Shuffle the elements in the given array. */
150  public static <T> T[] shuffle(final T[] array) {
151    if (array != null && array.length > 0) {
152      final Random random = getRandom();
153      for (int n = array.length; n > 1; ) {
154        final int randomIndex = random.nextInt(n);
155        n--;
156        if (n != randomIndex) {
157          final T tmp = array[randomIndex];
158          array[randomIndex] = array[n];
159          array[n] = tmp;
160        }
161      }
162    }
163    return array;
164  }
165
166  /**
167   * Compartor for sorting DataNodeInfo[] based on decommissioned states.
168   * Decommissioned nodes are moved to the end of the array on sorting with
169   * this compartor.
170   */
171  public static final Comparator<DatanodeInfo> DECOM_COMPARATOR = 
172    new Comparator<DatanodeInfo>() {
173      @Override
174      public int compare(DatanodeInfo a, DatanodeInfo b) {
175        return a.isDecommissioned() == b.isDecommissioned() ? 0 : 
176          a.isDecommissioned() ? 1 : -1;
177      }
178    };
179    
180      
181  /**
182   * Comparator for sorting DataNodeInfo[] based on decommissioned/stale states.
183   * Decommissioned/stale nodes are moved to the end of the array on sorting
184   * with this comparator.
185   */ 
186  @InterfaceAudience.Private 
187  public static class DecomStaleComparator implements Comparator<DatanodeInfo> {
188    private final long staleInterval;
189
190    /**
191     * Constructor of DecomStaleComparator
192     * 
193     * @param interval
194     *          The time interval for marking datanodes as stale is passed from
195     *          outside, since the interval may be changed dynamically
196     */
197    public DecomStaleComparator(long interval) {
198      this.staleInterval = interval;
199    }
200
201    @Override
202    public int compare(DatanodeInfo a, DatanodeInfo b) {
203      // Decommissioned nodes will still be moved to the end of the list
204      if (a.isDecommissioned()) {
205        return b.isDecommissioned() ? 0 : 1;
206      } else if (b.isDecommissioned()) {
207        return -1;
208      }
209      // Stale nodes will be moved behind the normal nodes
210      boolean aStale = a.isStale(staleInterval);
211      boolean bStale = b.isStale(staleInterval);
212      return aStale == bStale ? 0 : (aStale ? 1 : -1);
213    }
214  }    
215    
216  /**
217   * Address matcher for matching an address to local address
218   */
219  static final AddressMatcher LOCAL_ADDRESS_MATCHER = new AddressMatcher() {
220    @Override
221    public boolean match(InetSocketAddress s) {
222      return NetUtils.isLocalAddress(s.getAddress());
223    };
224  };
225  
226  /**
227   * Whether the pathname is valid.  Currently prohibits relative paths, 
228   * names which contain a ":" or "//", or other non-canonical paths.
229   */
230  public static boolean isValidName(String src) {
231    // Path must be absolute.
232    if (!src.startsWith(Path.SEPARATOR)) {
233      return false;
234    }
235      
236    // Check for ".." "." ":" "/"
237    String[] components = StringUtils.split(src, '/');
238    for (int i = 0; i < components.length; i++) {
239      String element = components[i];
240      if (element.equals(".")  ||
241          (element.indexOf(":") >= 0)  ||
242          (element.indexOf("/") >= 0)) {
243        return false;
244      }
245      // ".." is allowed in path starting with /.reserved/.inodes
246      if (element.equals("..")) {
247        if (components.length > 4
248            && components[1].equals(FSDirectory.DOT_RESERVED_STRING)
249            && components[2].equals(FSDirectory.DOT_INODES_STRING)) {
250          continue;
251        }
252        return false;
253      }
254      // The string may start or end with a /, but not have
255      // "//" in the middle.
256      if (element.isEmpty() && i != components.length - 1 &&
257          i != 0) {
258        return false;
259      }
260    }
261    return true;
262  }
263
264  /**
265   * Checks if a string is a valid path component. For instance, components
266   * cannot contain a ":" or "/", and cannot be equal to a reserved component
267   * like ".snapshot".
268   * <p>
269   * The primary use of this method is for validating paths when loading the
270   * FSImage. During normal NN operation, paths are sometimes allowed to
271   * contain reserved components.
272   * 
273   * @return If component is valid
274   */
275  public static boolean isValidNameForComponent(String component) {
276    if (component.equals(".") ||
277        component.equals("..") ||
278        component.indexOf(":") >= 0 ||
279        component.indexOf("/") >= 0) {
280      return false;
281    }
282    return !isReservedPathComponent(component);
283  }
284
285
286  /**
287   * Returns if the component is reserved.
288   * 
289   * <p>
290   * Note that some components are only reserved under certain directories, e.g.
291   * "/.reserved" is reserved, while "/hadoop/.reserved" is not.
292   * @return true, if the component is reserved
293   */
294  public static boolean isReservedPathComponent(String component) {
295    for (String reserved : HdfsConstants.RESERVED_PATH_COMPONENTS) {
296      if (component.equals(reserved)) {
297        return true;
298      }
299    }
300    return false;
301  }
302
303  /**
304   * Converts a byte array to a string using UTF8 encoding.
305   */
306  public static String bytes2String(byte[] bytes) {
307    return bytes2String(bytes, 0, bytes.length);
308  }
309  
310  /**
311   * Decode a specific range of bytes of the given byte array to a string
312   * using UTF8.
313   * 
314   * @param bytes The bytes to be decoded into characters
315   * @param offset The index of the first byte to decode
316   * @param length The number of bytes to decode
317   * @return The decoded string
318   */
319  public static String bytes2String(byte[] bytes, int offset, int length) {
320    try {
321      return new String(bytes, offset, length, "UTF8");
322    } catch(UnsupportedEncodingException e) {
323      assert false : "UTF8 encoding is not supported ";
324    }
325    return null;
326  }
327
328  /**
329   * Converts a string to a byte array using UTF8 encoding.
330   */
331  public static byte[] string2Bytes(String str) {
332    return str.getBytes(Charsets.UTF_8);
333  }
334
335  /**
336   * Given a list of path components returns a path as a UTF8 String
337   */
338  public static String byteArray2PathString(byte[][] pathComponents) {
339    if (pathComponents.length == 0) {
340      return "";
341    } else if (pathComponents.length == 1
342        && (pathComponents[0] == null || pathComponents[0].length == 0)) {
343      return Path.SEPARATOR;
344    }
345    StringBuilder result = new StringBuilder();
346    for (int i = 0; i < pathComponents.length; i++) {
347      result.append(new String(pathComponents[i], Charsets.UTF_8));
348      if (i < pathComponents.length - 1) {
349        result.append(Path.SEPARATOR_CHAR);
350      }
351    }
352    return result.toString();
353  }
354
355  /**
356   * Converts a list of path components into a path using Path.SEPARATOR.
357   * 
358   * @param components Path components
359   * @return Combined path as a UTF-8 string
360   */
361  public static String strings2PathString(String[] components) {
362    if (components.length == 0) {
363      return "";
364    }
365    if (components.length == 1) {
366      if (components[0] == null || components[0].isEmpty()) {
367        return Path.SEPARATOR;
368      }
369    }
370    return Joiner.on(Path.SEPARATOR).join(components);
371  }
372
373  /**
374   * Given a list of path components returns a byte array
375   */
376  public static byte[] byteArray2bytes(byte[][] pathComponents) {
377    if (pathComponents.length == 0) {
378      return EMPTY_BYTES;
379    } else if (pathComponents.length == 1
380        && (pathComponents[0] == null || pathComponents[0].length == 0)) {
381      return new byte[]{(byte) Path.SEPARATOR_CHAR};
382    }
383    int length = 0;
384    for (int i = 0; i < pathComponents.length; i++) {
385      length += pathComponents[i].length;
386      if (i < pathComponents.length - 1) {
387        length++; // for SEPARATOR
388      }
389    }
390    byte[] path = new byte[length];
391    int index = 0;
392    for (int i = 0; i < pathComponents.length; i++) {
393      System.arraycopy(pathComponents[i], 0, path, index,
394          pathComponents[i].length);
395      index += pathComponents[i].length;
396      if (i < pathComponents.length - 1) {
397        path[index] = (byte) Path.SEPARATOR_CHAR;
398        index++;
399      }
400    }
401    return path;
402  }
403
404  /** Convert an object representing a path to a string. */
405  public static String path2String(final Object path) {
406    return path == null? null
407        : path instanceof String? (String)path
408        : path instanceof byte[][]? byteArray2PathString((byte[][])path)
409        : path.toString();
410  }
411
412  /**
413   * Splits the array of bytes into array of arrays of bytes
414   * on byte separator
415   * @param bytes the array of bytes to split
416   * @param separator the delimiting byte
417   */
418  public static byte[][] bytes2byteArray(byte[] bytes, byte separator) {
419    return bytes2byteArray(bytes, bytes.length, separator);
420  }
421
422  /**
423   * Splits first len bytes in bytes to array of arrays of bytes
424   * on byte separator
425   * @param bytes the byte array to split
426   * @param len the number of bytes to split
427   * @param separator the delimiting byte
428   */
429  public static byte[][] bytes2byteArray(byte[] bytes,
430                                         int len,
431                                         byte separator) {
432    assert len <= bytes.length;
433    int splits = 0;
434    if (len == 0) {
435      return new byte[][]{null};
436    }
437    // Count the splits. Omit multiple separators and the last one
438    for (int i = 0; i < len; i++) {
439      if (bytes[i] == separator) {
440        splits++;
441      }
442    }
443    int last = len - 1;
444    while (last > -1 && bytes[last--] == separator) {
445      splits--;
446    }
447    if (splits == 0 && bytes[0] == separator) {
448      return new byte[][]{null};
449    }
450    splits++;
451    byte[][] result = new byte[splits][];
452    int startIndex = 0;
453    int nextIndex = 0;
454    int index = 0;
455    // Build the splits
456    while (index < splits) {
457      while (nextIndex < len && bytes[nextIndex] != separator) {
458        nextIndex++;
459      }
460      result[index] = new byte[nextIndex - startIndex];
461      System.arraycopy(bytes, startIndex, result[index], 0, nextIndex
462              - startIndex);
463      index++;
464      startIndex = nextIndex + 1;
465      nextIndex = startIndex;
466    }
467    return result;
468  }
469  
470  /**
471   * Convert a LocatedBlocks to BlockLocations[]
472   * @param blocks a LocatedBlocks
473   * @return an array of BlockLocations
474   */
475  public static BlockLocation[] locatedBlocks2Locations(LocatedBlocks blocks) {
476    if (blocks == null) {
477      return new BlockLocation[0];
478    }
479    return locatedBlocks2Locations(blocks.getLocatedBlocks());
480  }
481  
482  /**
483   * Convert a List<LocatedBlock> to BlockLocation[]
484   * @param blocks A List<LocatedBlock> to be converted
485   * @return converted array of BlockLocation
486   */
487  public static BlockLocation[] locatedBlocks2Locations(List<LocatedBlock> blocks) {
488    if (blocks == null) {
489      return new BlockLocation[0];
490    }
491    int nrBlocks = blocks.size();
492    BlockLocation[] blkLocations = new BlockLocation[nrBlocks];
493    if (nrBlocks == 0) {
494      return blkLocations;
495    }
496    int idx = 0;
497    for (LocatedBlock blk : blocks) {
498      assert idx < nrBlocks : "Incorrect index";
499      DatanodeInfo[] locations = blk.getLocations();
500      String[] hosts = new String[locations.length];
501      String[] xferAddrs = new String[locations.length];
502      String[] racks = new String[locations.length];
503      for (int hCnt = 0; hCnt < locations.length; hCnt++) {
504        hosts[hCnt] = locations[hCnt].getHostName();
505        xferAddrs[hCnt] = locations[hCnt].getXferAddr();
506        NodeBase node = new NodeBase(xferAddrs[hCnt], 
507                                     locations[hCnt].getNetworkLocation());
508        racks[hCnt] = node.toString();
509      }
510      DatanodeInfo[] cachedLocations = blk.getCachedLocations();
511      String[] cachedHosts = new String[cachedLocations.length];
512      for (int i=0; i<cachedLocations.length; i++) {
513        cachedHosts[i] = cachedLocations[i].getHostName();
514      }
515      blkLocations[idx] = new BlockLocation(xferAddrs, hosts, cachedHosts,
516                                            racks,
517                                            blk.getStartOffset(),
518                                            blk.getBlockSize(),
519                                            blk.isCorrupt());
520      idx++;
521    }
522    return blkLocations;
523  }
524
525  /**
526   * Returns collection of nameservice Ids from the configuration.
527   * @param conf configuration
528   * @return collection of nameservice Ids, or null if not specified
529   */
530  public static Collection<String> getNameServiceIds(Configuration conf) {
531    return conf.getTrimmedStringCollection(DFS_NAMESERVICES);
532  }
533
534  /**
535   * @return <code>coll</code> if it is non-null and non-empty. Otherwise,
536   * returns a list with a single null value.
537   */
538  private static Collection<String> emptyAsSingletonNull(Collection<String> coll) {
539    if (coll == null || coll.isEmpty()) {
540      return Collections.singletonList(null);
541    } else {
542      return coll;
543    }
544  }
545  
546  /**
547   * Namenode HighAvailability related configuration.
548   * Returns collection of namenode Ids from the configuration. One logical id
549   * for each namenode in the in the HA setup.
550   * 
551   * @param conf configuration
552   * @param nsId the nameservice ID to look at, or null for non-federated 
553   * @return collection of namenode Ids
554   */
555  public static Collection<String> getNameNodeIds(Configuration conf, String nsId) {
556    String key = addSuffix(DFS_HA_NAMENODES_KEY_PREFIX, nsId);
557    return conf.getTrimmedStringCollection(key);
558  }
559  
560  /**
561   * Given a list of keys in the order of preference, returns a value
562   * for the key in the given order from the configuration.
563   * @param defaultValue default value to return, when key was not found
564   * @param keySuffix suffix to add to the key, if it is not null
565   * @param conf Configuration
566   * @param keys list of keys in the order of preference
567   * @return value of the key or default if a key was not found in configuration
568   */
569  private static String getConfValue(String defaultValue, String keySuffix,
570      Configuration conf, String... keys) {
571    String value = null;
572    for (String key : keys) {
573      key = addSuffix(key, keySuffix);
574      value = conf.get(key);
575      if (value != null) {
576        break;
577      }
578    }
579    if (value == null) {
580      value = defaultValue;
581    }
582    return value;
583  }
584  
585  /** Add non empty and non null suffix to a key */
586  private static String addSuffix(String key, String suffix) {
587    if (suffix == null || suffix.isEmpty()) {
588      return key;
589    }
590    assert !suffix.startsWith(".") :
591      "suffix '" + suffix + "' should not already have '.' prepended.";
592    return key + "." + suffix;
593  }
594  
595  /** Concatenate list of suffix strings '.' separated */
596  private static String concatSuffixes(String... suffixes) {
597    if (suffixes == null) {
598      return null;
599    }
600    return Joiner.on(".").skipNulls().join(suffixes);
601  }
602  
603  /**
604   * Return configuration key of format key.suffix1.suffix2...suffixN
605   */
606  public static String addKeySuffixes(String key, String... suffixes) {
607    String keySuffix = concatSuffixes(suffixes);
608    return addSuffix(key, keySuffix);
609  }
610  
611  /**
612   * Returns the configured address for all NameNodes in the cluster.
613   * @param conf configuration
614   * @param defaultAddress default address to return in case key is not found.
615   * @param keys Set of keys to look for in the order of preference
616   * @return a map(nameserviceId to map(namenodeId to InetSocketAddress))
617   */
618  private static Map<String, Map<String, InetSocketAddress>>
619    getAddresses(Configuration conf,
620      String defaultAddress, String... keys) {
621    Collection<String> nameserviceIds = getNameServiceIds(conf);
622    
623    // Look for configurations of the form <key>[.<nameserviceId>][.<namenodeId>]
624    // across all of the configured nameservices and namenodes.
625    Map<String, Map<String, InetSocketAddress>> ret = Maps.newLinkedHashMap();
626    for (String nsId : emptyAsSingletonNull(nameserviceIds)) {
627      Map<String, InetSocketAddress> isas =
628        getAddressesForNameserviceId(conf, nsId, defaultAddress, keys);
629      if (!isas.isEmpty()) {
630        ret.put(nsId, isas);
631      }
632    }
633    return ret;
634  }
635  
636  /**
637   * Get all of the RPC addresses of the individual NNs in a given nameservice.
638   * 
639   * @param conf Configuration
640   * @param nsId the nameservice whose NNs addresses we want.
641   * @param defaultValue default address to return in case key is not found.
642   * @return A map from nnId -> RPC address of each NN in the nameservice.
643   */
644  public static Map<String, InetSocketAddress> getRpcAddressesForNameserviceId(
645      Configuration conf, String nsId, String defaultValue) {
646    return getAddressesForNameserviceId(conf, nsId, defaultValue,
647        DFS_NAMENODE_RPC_ADDRESS_KEY);
648  }
649
650  private static Map<String, InetSocketAddress> getAddressesForNameserviceId(
651      Configuration conf, String nsId, String defaultValue,
652      String... keys) {
653    Collection<String> nnIds = getNameNodeIds(conf, nsId);
654    Map<String, InetSocketAddress> ret = Maps.newHashMap();
655    for (String nnId : emptyAsSingletonNull(nnIds)) {
656      String suffix = concatSuffixes(nsId, nnId);
657      String address = getConfValue(defaultValue, suffix, conf, keys);
658      if (address != null) {
659        InetSocketAddress isa = NetUtils.createSocketAddr(address);
660        if (isa.isUnresolved()) {
661          LOG.warn("Namenode for " + nsId +
662                   " remains unresolved for ID " + nnId +
663                   ".  Check your hdfs-site.xml file to " +
664                   "ensure namenodes are configured properly.");
665        }
666        ret.put(nnId, isa);
667      }
668    }
669    return ret;
670  }
671
672  /**
673   * @return a collection of all configured NN Kerberos principals.
674   */
675  public static Set<String> getAllNnPrincipals(Configuration conf) throws IOException {
676    Set<String> principals = new HashSet<String>();
677    for (String nsId : DFSUtil.getNameServiceIds(conf)) {
678      if (HAUtil.isHAEnabled(conf, nsId)) {
679        for (String nnId : DFSUtil.getNameNodeIds(conf, nsId)) {
680          Configuration confForNn = new Configuration(conf);
681          NameNode.initializeGenericKeys(confForNn, nsId, nnId);
682          String principal = SecurityUtil.getServerPrincipal(confForNn
683              .get(DFSConfigKeys.DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY),
684              NameNode.getAddress(confForNn).getHostName());
685          principals.add(principal);
686        }
687      } else {
688        Configuration confForNn = new Configuration(conf);
689        NameNode.initializeGenericKeys(confForNn, nsId, null);
690        String principal = SecurityUtil.getServerPrincipal(confForNn
691            .get(DFSConfigKeys.DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY),
692            NameNode.getAddress(confForNn).getHostName());
693        principals.add(principal);
694      }
695    }
696
697    return principals;
698  }
699
700  /**
701   * Returns list of InetSocketAddress corresponding to HA NN RPC addresses from
702   * the configuration.
703   * 
704   * @param conf configuration
705   * @return list of InetSocketAddresses
706   */
707  public static Map<String, Map<String, InetSocketAddress>> getHaNnRpcAddresses(
708      Configuration conf) {
709    return getAddresses(conf, null, DFSConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY);
710  }
711
712  /**
713   * Returns list of InetSocketAddress corresponding to HA NN HTTP addresses from
714   * the configuration.
715   *
716   * @return list of InetSocketAddresses
717   */
718  public static Map<String, Map<String, InetSocketAddress>> getHaNnWebHdfsAddresses(
719      Configuration conf, String scheme) {
720    if (WebHdfsFileSystem.SCHEME.equals(scheme)) {
721      return getAddresses(conf, null,
722          DFSConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_KEY);
723    } else if (SWebHdfsFileSystem.SCHEME.equals(scheme)) {
724      return getAddresses(conf, null,
725          DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY);
726    } else {
727      throw new IllegalArgumentException("Unsupported scheme: " + scheme);
728    }
729  }
730
731  /**
732   * Returns list of InetSocketAddress corresponding to  backup node rpc 
733   * addresses from the configuration.
734   * 
735   * @param conf configuration
736   * @return list of InetSocketAddresses
737   * @throws IOException on error
738   */
739  public static Map<String, Map<String, InetSocketAddress>> getBackupNodeAddresses(
740      Configuration conf) throws IOException {
741    Map<String, Map<String, InetSocketAddress>> addressList = getAddresses(conf,
742        null, DFS_NAMENODE_BACKUP_ADDRESS_KEY);
743    if (addressList.isEmpty()) {
744      throw new IOException("Incorrect configuration: backup node address "
745          + DFS_NAMENODE_BACKUP_ADDRESS_KEY + " is not configured.");
746    }
747    return addressList;
748  }
749
750  /**
751   * Returns list of InetSocketAddresses of corresponding to secondary namenode
752   * http addresses from the configuration.
753   * 
754   * @param conf configuration
755   * @return list of InetSocketAddresses
756   * @throws IOException on error
757   */
758  public static Map<String, Map<String, InetSocketAddress>> getSecondaryNameNodeAddresses(
759      Configuration conf) throws IOException {
760    Map<String, Map<String, InetSocketAddress>> addressList = getAddresses(conf, null,
761        DFS_NAMENODE_SECONDARY_HTTP_ADDRESS_KEY);
762    if (addressList.isEmpty()) {
763      throw new IOException("Incorrect configuration: secondary namenode address "
764          + DFS_NAMENODE_SECONDARY_HTTP_ADDRESS_KEY + " is not configured.");
765    }
766    return addressList;
767  }
768
769  /**
770   * Returns list of InetSocketAddresses corresponding to namenodes from the
771   * configuration. Note this is to be used by datanodes to get the list of
772   * namenode addresses to talk to.
773   * 
774   * Returns namenode address specifically configured for datanodes (using
775   * service ports), if found. If not, regular RPC address configured for other
776   * clients is returned.
777   * 
778   * @param conf configuration
779   * @return list of InetSocketAddress
780   * @throws IOException on error
781   */
782  public static Map<String, Map<String, InetSocketAddress>> getNNServiceRpcAddresses(
783      Configuration conf) throws IOException {
784    // Use default address as fall back
785    String defaultAddress;
786    try {
787      defaultAddress = NetUtils.getHostPortString(NameNode.getAddress(conf));
788    } catch (IllegalArgumentException e) {
789      defaultAddress = null;
790    }
791    
792    Map<String, Map<String, InetSocketAddress>> addressList =
793      getAddresses(conf, defaultAddress,
794        DFS_NAMENODE_SERVICE_RPC_ADDRESS_KEY, DFS_NAMENODE_RPC_ADDRESS_KEY);
795    if (addressList.isEmpty()) {
796      throw new IOException("Incorrect configuration: namenode address "
797          + DFS_NAMENODE_SERVICE_RPC_ADDRESS_KEY + " or "  
798          + DFS_NAMENODE_RPC_ADDRESS_KEY
799          + " is not configured.");
800    }
801    return addressList;
802  }
803  
804  /**
805   * Flatten the given map, as returned by other functions in this class,
806   * into a flat list of {@link ConfiguredNNAddress} instances.
807   */
808  public static List<ConfiguredNNAddress> flattenAddressMap(
809      Map<String, Map<String, InetSocketAddress>> map) {
810    List<ConfiguredNNAddress> ret = Lists.newArrayList();
811    
812    for (Map.Entry<String, Map<String, InetSocketAddress>> entry :
813      map.entrySet()) {
814      String nsId = entry.getKey();
815      Map<String, InetSocketAddress> nnMap = entry.getValue();
816      for (Map.Entry<String, InetSocketAddress> e2 : nnMap.entrySet()) {
817        String nnId = e2.getKey();
818        InetSocketAddress addr = e2.getValue();
819        
820        ret.add(new ConfiguredNNAddress(nsId, nnId, addr));
821      }
822    }
823    return ret;
824  }
825
826  /**
827   * Format the given map, as returned by other functions in this class,
828   * into a string suitable for debugging display. The format of this string
829   * should not be considered an interface, and is liable to change.
830   */
831  public static String addressMapToString(
832      Map<String, Map<String, InetSocketAddress>> map) {
833    StringBuilder b = new StringBuilder();
834    for (Map.Entry<String, Map<String, InetSocketAddress>> entry :
835         map.entrySet()) {
836      String nsId = entry.getKey();
837      Map<String, InetSocketAddress> nnMap = entry.getValue();
838      b.append("Nameservice <").append(nsId).append(">:").append("\n");
839      for (Map.Entry<String, InetSocketAddress> e2 : nnMap.entrySet()) {
840        b.append("  NN ID ").append(e2.getKey())
841          .append(" => ").append(e2.getValue()).append("\n");
842      }
843    }
844    return b.toString();
845  }
846  
847  public static String nnAddressesAsString(Configuration conf) {
848    Map<String, Map<String, InetSocketAddress>> addresses =
849      getHaNnRpcAddresses(conf);
850    return addressMapToString(addresses);
851  }
852
853  /**
854   * Represent one of the NameNodes configured in the cluster.
855   */
856  public static class ConfiguredNNAddress {
857    private final String nameserviceId;
858    private final String namenodeId;
859    private final InetSocketAddress addr;
860
861    private ConfiguredNNAddress(String nameserviceId, String namenodeId,
862        InetSocketAddress addr) {
863      this.nameserviceId = nameserviceId;
864      this.namenodeId = namenodeId;
865      this.addr = addr;
866    }
867
868    public String getNameserviceId() {
869      return nameserviceId;
870    }
871
872    public String getNamenodeId() {
873      return namenodeId;
874    }
875
876    public InetSocketAddress getAddress() {
877      return addr;
878    }
879    
880    @Override
881    public String toString() {
882      return "ConfiguredNNAddress[nsId=" + nameserviceId + ";" +
883        "nnId=" + namenodeId + ";addr=" + addr + "]";
884    }
885  }
886  
887  /**
888   * Get a URI for each configured nameservice. If a nameservice is
889   * HA-enabled, then the logical URI of the nameservice is returned. If the
890   * nameservice is not HA-enabled, then a URI corresponding to an RPC address
891   * of the single NN for that nameservice is returned, preferring the service
892   * RPC address over the client RPC address.
893   * 
894   * @param conf configuration
895   * @return a collection of all configured NN URIs, preferring service
896   *         addresses
897   */
898  public static Collection<URI> getNsServiceRpcUris(Configuration conf) {
899    return getNameServiceUris(conf,
900        DFSConfigKeys.DFS_NAMENODE_SERVICE_RPC_ADDRESS_KEY,
901        DFSConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY);
902  }
903
904  /**
905   * Get a URI for each configured nameservice. If a nameservice is
906   * HA-enabled, then the logical URI of the nameservice is returned. If the
907   * nameservice is not HA-enabled, then a URI corresponding to the address of
908   * the single NN for that nameservice is returned.
909   * 
910   * @param conf configuration
911   * @param keys configuration keys to try in order to get the URI for non-HA
912   *        nameservices
913   * @return a collection of all configured NN URIs
914   */
915  public static Collection<URI> getNameServiceUris(Configuration conf,
916      String... keys) {
917    Set<URI> ret = new HashSet<URI>();
918    
919    // We're passed multiple possible configuration keys for any given NN or HA
920    // nameservice, and search the config in order of these keys. In order to
921    // make sure that a later config lookup (e.g. fs.defaultFS) doesn't add a
922    // URI for a config key for which we've already found a preferred entry, we
923    // keep track of non-preferred keys here.
924    Set<URI> nonPreferredUris = new HashSet<URI>();
925    
926    for (String nsId : getNameServiceIds(conf)) {
927      if (HAUtil.isHAEnabled(conf, nsId)) {
928        // Add the logical URI of the nameservice.
929        try {
930          ret.add(new URI(HdfsConstants.HDFS_URI_SCHEME + "://" + nsId));
931        } catch (URISyntaxException ue) {
932          throw new IllegalArgumentException(ue);
933        }
934      } else {
935        // Add the URI corresponding to the address of the NN.
936        boolean uriFound = false;
937        for (String key : keys) {
938          String addr = conf.get(concatSuffixes(key, nsId));
939          if (addr != null) {
940            URI uri = createUri(HdfsConstants.HDFS_URI_SCHEME,
941                NetUtils.createSocketAddr(addr));
942            if (!uriFound) {
943              uriFound = true;
944              ret.add(uri);
945            } else {
946              nonPreferredUris.add(uri);
947            }
948          }
949        }
950      }
951    }
952    
953    // Add the generic configuration keys.
954    boolean uriFound = false;
955    for (String key : keys) {
956      String addr = conf.get(key);
957      if (addr != null) {
958        URI uri = createUri("hdfs", NetUtils.createSocketAddr(addr));
959        if (!uriFound) {
960          uriFound = true;
961          ret.add(uri);
962        } else {
963          nonPreferredUris.add(uri);
964        }
965      }
966    }
967    
968    // Add the default URI if it is an HDFS URI.
969    URI defaultUri = FileSystem.getDefaultUri(conf);
970    // checks if defaultUri is ip:port format
971    // and convert it to hostname:port format
972    if (defaultUri != null && (defaultUri.getPort() != -1)) {
973      defaultUri = createUri(defaultUri.getScheme(),
974          NetUtils.createSocketAddr(defaultUri.getHost(), 
975              defaultUri.getPort()));
976    }
977    if (defaultUri != null &&
978        HdfsConstants.HDFS_URI_SCHEME.equals(defaultUri.getScheme()) &&
979        !nonPreferredUris.contains(defaultUri)) {
980      ret.add(defaultUri);
981    }
982    
983    return ret;
984  }
985
986  /**
987   * Given the InetSocketAddress this method returns the nameservice Id
988   * corresponding to the key with matching address, by doing a reverse 
989   * lookup on the list of nameservices until it finds a match.
990   * 
991   * Since the process of resolving URIs to Addresses is slightly expensive,
992   * this utility method should not be used in performance-critical routines.
993   * 
994   * @param conf - configuration
995   * @param address - InetSocketAddress for configured communication with NN.
996   *     Configured addresses are typically given as URIs, but we may have to
997   *     compare against a URI typed in by a human, or the server name may be
998   *     aliased, so we compare unambiguous InetSocketAddresses instead of just
999   *     comparing URI substrings.
1000   * @param keys - list of configured communication parameters that should
1001   *     be checked for matches.  For example, to compare against RPC addresses,
1002   *     provide the list DFS_NAMENODE_SERVICE_RPC_ADDRESS_KEY,
1003   *     DFS_NAMENODE_RPC_ADDRESS_KEY.  Use the generic parameter keys,
1004   *     not the NameServiceId-suffixed keys.
1005   * @return nameserviceId, or null if no match found
1006   */
1007  public static String getNameServiceIdFromAddress(final Configuration conf, 
1008      final InetSocketAddress address, String... keys) {
1009    // Configuration with a single namenode and no nameserviceId
1010    String[] ids = getSuffixIDs(conf, address, keys);
1011    return (ids != null) ? ids[0] : null;
1012  }
1013  
1014  /**
1015   * return server http or https address from the configuration for a
1016   * given namenode rpc address.
1017   * @param namenodeAddr - namenode RPC address
1018   * @param conf configuration
1019   * @param scheme - the scheme (http / https)
1020   * @return server http or https address
1021   * @throws IOException 
1022   */
1023  public static URI getInfoServer(InetSocketAddress namenodeAddr,
1024      Configuration conf, String scheme) throws IOException {
1025    String[] suffixes = null;
1026    if (namenodeAddr != null) {
1027      // if non-default namenode, try reverse look up 
1028      // the nameServiceID if it is available
1029      suffixes = getSuffixIDs(conf, namenodeAddr,
1030          DFSConfigKeys.DFS_NAMENODE_SERVICE_RPC_ADDRESS_KEY,
1031          DFSConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY);
1032    }
1033
1034    String authority;
1035    if ("http".equals(scheme)) {
1036      authority = getSuffixedConf(conf, DFS_NAMENODE_HTTP_ADDRESS_KEY,
1037          DFS_NAMENODE_HTTP_ADDRESS_DEFAULT, suffixes);
1038    } else if ("https".equals(scheme)) {
1039      authority = getSuffixedConf(conf, DFS_NAMENODE_HTTPS_ADDRESS_KEY,
1040          DFS_NAMENODE_HTTPS_ADDRESS_DEFAULT, suffixes);
1041    } else {
1042      throw new IllegalArgumentException("Invalid scheme:" + scheme);
1043    }
1044
1045    if (namenodeAddr != null) {
1046      authority = substituteForWildcardAddress(authority,
1047          namenodeAddr.getHostName());
1048    }
1049    return URI.create(scheme + "://" + authority);
1050  }
1051
1052  /**
1053   * Lookup the HTTP / HTTPS address of the namenode, and replace its hostname
1054   * with defaultHost when it found out that the address is a wildcard / local
1055   * address.
1056   *
1057   * @param defaultHost
1058   *          The default host name of the namenode.
1059   * @param conf
1060   *          The configuration
1061   * @param scheme
1062   *          HTTP or HTTPS
1063   * @throws IOException
1064   */
1065  public static URI getInfoServerWithDefaultHost(String defaultHost,
1066      Configuration conf, final String scheme) throws IOException {
1067    URI configuredAddr = getInfoServer(null, conf, scheme);
1068    String authority = substituteForWildcardAddress(
1069        configuredAddr.getAuthority(), defaultHost);
1070    return URI.create(scheme + "://" + authority);
1071  }
1072
1073  /**
1074   * Determine whether HTTP or HTTPS should be used to connect to the remote
1075   * server. Currently the client only connects to the server via HTTPS if the
1076   * policy is set to HTTPS_ONLY.
1077   *
1078   * @return the scheme (HTTP / HTTPS)
1079   */
1080  public static String getHttpClientScheme(Configuration conf) {
1081    HttpConfig.Policy policy = DFSUtil.getHttpPolicy(conf);
1082    return policy == HttpConfig.Policy.HTTPS_ONLY ? "https" : "http";
1083  }
1084
1085  /**
1086   * Substitute a default host in the case that an address has been configured
1087   * with a wildcard. This is used, for example, when determining the HTTP
1088   * address of the NN -- if it's configured to bind to 0.0.0.0, we want to
1089   * substitute the hostname from the filesystem URI rather than trying to
1090   * connect to 0.0.0.0.
1091   * @param configuredAddress the address found in the configuration
1092   * @param defaultHost the host to substitute with, if configuredAddress
1093   * is a local/wildcard address.
1094   * @return the substituted address
1095   * @throws IOException if it is a wildcard address and security is enabled
1096   */
1097  @VisibleForTesting
1098  static String substituteForWildcardAddress(String configuredAddress,
1099    String defaultHost) throws IOException {
1100    InetSocketAddress sockAddr = NetUtils.createSocketAddr(configuredAddress);
1101    InetSocketAddress defaultSockAddr = NetUtils.createSocketAddr(defaultHost
1102        + ":0");
1103    final InetAddress addr = sockAddr.getAddress();
1104    if (addr != null && addr.isAnyLocalAddress()) {
1105      if (UserGroupInformation.isSecurityEnabled() &&
1106          defaultSockAddr.getAddress().isAnyLocalAddress()) {
1107        throw new IOException("Cannot use a wildcard address with security. " +
1108            "Must explicitly set bind address for Kerberos");
1109      }
1110      return defaultHost + ":" + sockAddr.getPort();
1111    } else {
1112      return configuredAddress;
1113    }
1114  }
1115  
1116  private static String getSuffixedConf(Configuration conf,
1117      String key, String defaultVal, String[] suffixes) {
1118    String ret = conf.get(DFSUtil.addKeySuffixes(key, suffixes));
1119    if (ret != null) {
1120      return ret;
1121    }
1122    return conf.get(key, defaultVal);
1123  }
1124  
1125  /**
1126   * Sets the node specific setting into generic configuration key. Looks up
1127   * value of "key.nameserviceId.namenodeId" and if found sets that value into 
1128   * generic key in the conf. If this is not found, falls back to
1129   * "key.nameserviceId" and then the unmodified key.
1130   *
1131   * Note that this only modifies the runtime conf.
1132   * 
1133   * @param conf
1134   *          Configuration object to lookup specific key and to set the value
1135   *          to the key passed. Note the conf object is modified.
1136   * @param nameserviceId
1137   *          nameservice Id to construct the node specific key. Pass null if
1138   *          federation is not configuration.
1139   * @param nnId
1140   *          namenode Id to construct the node specific key. Pass null if
1141   *          HA is not configured.
1142   * @param keys
1143   *          The key for which node specific value is looked up
1144   */
1145  public static void setGenericConf(Configuration conf,
1146      String nameserviceId, String nnId, String... keys) {
1147    for (String key : keys) {
1148      String value = conf.get(addKeySuffixes(key, nameserviceId, nnId));
1149      if (value != null) {
1150        conf.set(key, value);
1151        continue;
1152      }
1153      value = conf.get(addKeySuffixes(key, nameserviceId));
1154      if (value != null) {
1155        conf.set(key, value);
1156      }
1157    }
1158  }
1159  
1160  /** Return used as percentage of capacity */
1161  public static float getPercentUsed(long used, long capacity) {
1162    return capacity <= 0 ? 100 : (used * 100.0f)/capacity; 
1163  }
1164  
1165  /** Return remaining as percentage of capacity */
1166  public static float getPercentRemaining(long remaining, long capacity) {
1167    return capacity <= 0 ? 0 : (remaining * 100.0f)/capacity; 
1168  }
1169
1170  /** Convert percentage to a string. */
1171  public static String percent2String(double percentage) {
1172    return StringUtils.format("%.2f%%", percentage);
1173  }
1174
1175  /**
1176   * Round bytes to GiB (gibibyte)
1177   * @param bytes number of bytes
1178   * @return number of GiB
1179   */
1180  public static int roundBytesToGB(long bytes) {
1181    return Math.round((float)bytes/ 1024 / 1024 / 1024);
1182  }
1183  
1184  /** Create a {@link ClientDatanodeProtocol} proxy */
1185  public static ClientDatanodeProtocol createClientDatanodeProtocolProxy(
1186      DatanodeID datanodeid, Configuration conf, int socketTimeout,
1187      boolean connectToDnViaHostname, LocatedBlock locatedBlock) throws IOException {
1188    return new ClientDatanodeProtocolTranslatorPB(datanodeid, conf, socketTimeout,
1189        connectToDnViaHostname, locatedBlock);
1190  }
1191  
1192  /** Create {@link ClientDatanodeProtocol} proxy using kerberos ticket */
1193  public static ClientDatanodeProtocol createClientDatanodeProtocolProxy(
1194      DatanodeID datanodeid, Configuration conf, int socketTimeout,
1195      boolean connectToDnViaHostname) throws IOException {
1196    return new ClientDatanodeProtocolTranslatorPB(
1197        datanodeid, conf, socketTimeout, connectToDnViaHostname);
1198  }
1199  
1200  /** Create a {@link ClientDatanodeProtocol} proxy */
1201  public static ClientDatanodeProtocol createClientDatanodeProtocolProxy(
1202      InetSocketAddress addr, UserGroupInformation ticket, Configuration conf,
1203      SocketFactory factory) throws IOException {
1204    return new ClientDatanodeProtocolTranslatorPB(addr, ticket, conf, factory);
1205  }
1206
1207  /**
1208   * Get nameservice Id for the {@link NameNode} based on namenode RPC address
1209   * matching the local node address.
1210   */
1211  public static String getNamenodeNameServiceId(Configuration conf) {
1212    return getNameServiceId(conf, DFS_NAMENODE_RPC_ADDRESS_KEY);
1213  }
1214  
1215  /**
1216   * Get nameservice Id for the BackupNode based on backup node RPC address
1217   * matching the local node address.
1218   */
1219  public static String getBackupNameServiceId(Configuration conf) {
1220    return getNameServiceId(conf, DFS_NAMENODE_BACKUP_ADDRESS_KEY);
1221  }
1222  
1223  /**
1224   * Get nameservice Id for the secondary node based on secondary http address
1225   * matching the local node address.
1226   */
1227  public static String getSecondaryNameServiceId(Configuration conf) {
1228    return getNameServiceId(conf, DFS_NAMENODE_SECONDARY_HTTP_ADDRESS_KEY);
1229  }
1230  
1231  /**
1232   * Get the nameservice Id by matching the {@code addressKey} with the
1233   * the address of the local node. 
1234   * 
1235   * If {@link DFSConfigKeys#DFS_NAMESERVICE_ID} is not specifically
1236   * configured, and more than one nameservice Id is configured, this method 
1237   * determines the nameservice Id by matching the local node's address with the
1238   * configured addresses. When a match is found, it returns the nameservice Id
1239   * from the corresponding configuration key.
1240   * 
1241   * @param conf Configuration
1242   * @param addressKey configuration key to get the address.
1243   * @return nameservice Id on success, null if federation is not configured.
1244   * @throws HadoopIllegalArgumentException on error
1245   */
1246  private static String getNameServiceId(Configuration conf, String addressKey) {
1247    String nameserviceId = conf.get(DFS_NAMESERVICE_ID);
1248    if (nameserviceId != null) {
1249      return nameserviceId;
1250    }
1251    Collection<String> nsIds = getNameServiceIds(conf);
1252    if (1 == nsIds.size()) {
1253      return nsIds.toArray(new String[1])[0];
1254    }
1255    String nnId = conf.get(DFS_HA_NAMENODE_ID_KEY);
1256    
1257    return getSuffixIDs(conf, addressKey, null, nnId, LOCAL_ADDRESS_MATCHER)[0];
1258  }
1259  
1260  /**
1261   * Returns nameservice Id and namenode Id when the local host matches the
1262   * configuration parameter {@code addressKey}.<nameservice Id>.<namenode Id>
1263   * 
1264   * @param conf Configuration
1265   * @param addressKey configuration key corresponding to the address.
1266   * @param knownNsId only look at configs for the given nameservice, if not-null
1267   * @param knownNNId only look at configs for the given namenode, if not null
1268   * @param matcher matching criteria for matching the address
1269   * @return Array with nameservice Id and namenode Id on success. First element
1270   *         in the array is nameservice Id and second element is namenode Id.
1271   *         Null value indicates that the configuration does not have the the
1272   *         Id.
1273   * @throws HadoopIllegalArgumentException on error
1274   */
1275  static String[] getSuffixIDs(final Configuration conf, final String addressKey,
1276      String knownNsId, String knownNNId,
1277      final AddressMatcher matcher) {
1278    String nameserviceId = null;
1279    String namenodeId = null;
1280    int found = 0;
1281    
1282    Collection<String> nsIds = getNameServiceIds(conf);
1283    for (String nsId : emptyAsSingletonNull(nsIds)) {
1284      if (knownNsId != null && !knownNsId.equals(nsId)) {
1285        continue;
1286      }
1287      
1288      Collection<String> nnIds = getNameNodeIds(conf, nsId);
1289      for (String nnId : emptyAsSingletonNull(nnIds)) {
1290        if (LOG.isTraceEnabled()) {
1291          LOG.trace(String.format("addressKey: %s nsId: %s nnId: %s",
1292              addressKey, nsId, nnId));
1293        }
1294        if (knownNNId != null && !knownNNId.equals(nnId)) {
1295          continue;
1296        }
1297        String key = addKeySuffixes(addressKey, nsId, nnId);
1298        String addr = conf.get(key);
1299        if (addr == null) {
1300          continue;
1301        }
1302        InetSocketAddress s = null;
1303        try {
1304          s = NetUtils.createSocketAddr(addr);
1305        } catch (Exception e) {
1306          LOG.warn("Exception in creating socket address " + addr, e);
1307          continue;
1308        }
1309        if (!s.isUnresolved() && matcher.match(s)) {
1310          nameserviceId = nsId;
1311          namenodeId = nnId;
1312          found++;
1313        }
1314      }
1315    }
1316    if (found > 1) { // Only one address must match the local address
1317      String msg = "Configuration has multiple addresses that match "
1318          + "local node's address. Please configure the system with "
1319          + DFS_NAMESERVICE_ID + " and "
1320          + DFS_HA_NAMENODE_ID_KEY;
1321      throw new HadoopIllegalArgumentException(msg);
1322    }
1323    return new String[] { nameserviceId, namenodeId };
1324  }
1325  
1326  /**
1327   * For given set of {@code keys} adds nameservice Id and or namenode Id
1328   * and returns {nameserviceId, namenodeId} when address match is found.
1329   * @see #getSuffixIDs(Configuration, String, String, String, AddressMatcher)
1330   */
1331  static String[] getSuffixIDs(final Configuration conf,
1332      final InetSocketAddress address, final String... keys) {
1333    AddressMatcher matcher = new AddressMatcher() {
1334     @Override
1335      public boolean match(InetSocketAddress s) {
1336        return address.equals(s);
1337      } 
1338    };
1339    
1340    for (String key : keys) {
1341      String[] ids = getSuffixIDs(conf, key, null, null, matcher);
1342      if (ids != null && (ids [0] != null || ids[1] != null)) {
1343        return ids;
1344      }
1345    }
1346    return null;
1347  }
1348  
1349  private interface AddressMatcher {
1350    public boolean match(InetSocketAddress s);
1351  }
1352
1353  /** Create a URI from the scheme and address */
1354  public static URI createUri(String scheme, InetSocketAddress address) {
1355    try {
1356      return new URI(scheme, null, address.getHostName(), address.getPort(),
1357          null, null, null);
1358    } catch (URISyntaxException ue) {
1359      throw new IllegalArgumentException(ue);
1360    }
1361  }
1362  
1363  /**
1364   * Add protobuf based protocol to the {@link org.apache.hadoop.ipc.RPC.Server}
1365   * @param conf configuration
1366   * @param protocol Protocol interface
1367   * @param service service that implements the protocol
1368   * @param server RPC server to which the protocol & implementation is added to
1369   * @throws IOException
1370   */
1371  public static void addPBProtocol(Configuration conf, Class<?> protocol,
1372      BlockingService service, RPC.Server server) throws IOException {
1373    RPC.setProtocolEngine(conf, protocol, ProtobufRpcEngine.class);
1374    server.addProtocol(RPC.RpcKind.RPC_PROTOCOL_BUFFER, protocol, service);
1375  }
1376
1377  /**
1378   * Map a logical namenode ID to its service address. Use the given
1379   * nameservice if specified, or the configured one if none is given.
1380   *
1381   * @param conf Configuration
1382   * @param nsId which nameservice nnId is a part of, optional
1383   * @param nnId the namenode ID to get the service addr for
1384   * @return the service addr, null if it could not be determined
1385   */
1386  public static String getNamenodeServiceAddr(final Configuration conf,
1387      String nsId, String nnId) {
1388
1389    if (nsId == null) {
1390      nsId = getOnlyNameServiceIdOrNull(conf);
1391    }
1392
1393    String serviceAddrKey = concatSuffixes(
1394        DFSConfigKeys.DFS_NAMENODE_SERVICE_RPC_ADDRESS_KEY, nsId, nnId);
1395
1396    String addrKey = concatSuffixes(
1397        DFSConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY, nsId, nnId);
1398
1399    String serviceRpcAddr = conf.get(serviceAddrKey);
1400    if (serviceRpcAddr == null) {
1401      serviceRpcAddr = conf.get(addrKey);
1402    }
1403    return serviceRpcAddr;
1404  }
1405
1406  /**
1407   * If the configuration refers to only a single nameservice, return the
1408   * name of that nameservice. If it refers to 0 or more than 1, return null.
1409   */
1410  public static String getOnlyNameServiceIdOrNull(Configuration conf) {
1411    Collection<String> nsIds = getNameServiceIds(conf);
1412    if (1 == nsIds.size()) {
1413      return nsIds.toArray(new String[1])[0];
1414    } else {
1415      // No nameservice ID was given and more than one is configured
1416      return null;
1417    }
1418  }
1419  
1420  public static final Options helpOptions = new Options();
1421  public static final Option helpOpt = new Option("h", "help", false,
1422      "get help information");
1423
1424  static {
1425    helpOptions.addOption(helpOpt);
1426  }
1427
1428  /**
1429   * Parse the arguments for commands
1430   * 
1431   * @param args the argument to be parsed
1432   * @param helpDescription help information to be printed out
1433   * @param out Printer
1434   * @param printGenericCommandUsage whether to print the 
1435   *              generic command usage defined in ToolRunner
1436   * @return true when the argument matches help option, false if not
1437   */
1438  public static boolean parseHelpArgument(String[] args,
1439      String helpDescription, PrintStream out, boolean printGenericCommandUsage) {
1440    if (args.length == 1) {
1441      try {
1442        CommandLineParser parser = new PosixParser();
1443        CommandLine cmdLine = parser.parse(helpOptions, args);
1444        if (cmdLine.hasOption(helpOpt.getOpt())
1445            || cmdLine.hasOption(helpOpt.getLongOpt())) {
1446          // should print out the help information
1447          out.println(helpDescription + "\n");
1448          if (printGenericCommandUsage) {
1449            ToolRunner.printGenericCommandUsage(out);
1450          }
1451          return true;
1452        }
1453      } catch (ParseException pe) {
1454        return false;
1455      }
1456    }
1457    return false;
1458  }
1459  
1460  /**
1461   * Get DFS_NAMENODE_INVALIDATE_WORK_PCT_PER_ITERATION from configuration.
1462   * 
1463   * @param conf Configuration
1464   * @return Value of DFS_NAMENODE_INVALIDATE_WORK_PCT_PER_ITERATION
1465   */
1466  public static float getInvalidateWorkPctPerIteration(Configuration conf) {
1467    float blocksInvalidateWorkPct = conf.getFloat(
1468        DFSConfigKeys.DFS_NAMENODE_INVALIDATE_WORK_PCT_PER_ITERATION,
1469        DFSConfigKeys.DFS_NAMENODE_INVALIDATE_WORK_PCT_PER_ITERATION_DEFAULT);
1470    Preconditions.checkArgument(
1471        (blocksInvalidateWorkPct > 0 && blocksInvalidateWorkPct <= 1.0f),
1472        DFSConfigKeys.DFS_NAMENODE_INVALIDATE_WORK_PCT_PER_ITERATION +
1473        " = '" + blocksInvalidateWorkPct + "' is invalid. " +
1474        "It should be a positive, non-zero float value, not greater than 1.0f, " +
1475        "to indicate a percentage.");
1476    return blocksInvalidateWorkPct;
1477  }
1478
1479  /**
1480   * Get DFS_NAMENODE_REPLICATION_WORK_MULTIPLIER_PER_ITERATION from
1481   * configuration.
1482   * 
1483   * @param conf Configuration
1484   * @return Value of DFS_NAMENODE_REPLICATION_WORK_MULTIPLIER_PER_ITERATION
1485   */
1486  public static int getReplWorkMultiplier(Configuration conf) {
1487    int blocksReplWorkMultiplier = conf.getInt(
1488            DFSConfigKeys.DFS_NAMENODE_REPLICATION_WORK_MULTIPLIER_PER_ITERATION,
1489            DFSConfigKeys.DFS_NAMENODE_REPLICATION_WORK_MULTIPLIER_PER_ITERATION_DEFAULT);
1490    Preconditions.checkArgument(
1491        (blocksReplWorkMultiplier > 0),
1492        DFSConfigKeys.DFS_NAMENODE_REPLICATION_WORK_MULTIPLIER_PER_ITERATION +
1493        " = '" + blocksReplWorkMultiplier + "' is invalid. " +
1494        "It should be a positive, non-zero integer value.");
1495    return blocksReplWorkMultiplier;
1496  }
1497  
1498  /**
1499   * Get SPNEGO keytab Key from configuration
1500   * 
1501   * @param conf Configuration
1502   * @param defaultKey default key to be used for config lookup
1503   * @return DFS_WEB_AUTHENTICATION_KERBEROS_KEYTAB_KEY if the key is not empty
1504   *         else return defaultKey
1505   */
1506  public static String getSpnegoKeytabKey(Configuration conf, String defaultKey) {
1507    String value = 
1508        conf.get(DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBEROS_KEYTAB_KEY);
1509    return (value == null || value.isEmpty()) ?
1510        defaultKey : DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBEROS_KEYTAB_KEY;
1511  }
1512
1513  /**
1514   * Get http policy. Http Policy is chosen as follows:
1515   * <ol>
1516   * <li>If hadoop.ssl.enabled is set, http endpoints are not started. Only
1517   * https endpoints are started on configured https ports</li>
1518   * <li>This configuration is overridden by dfs.https.enable configuration, if
1519   * it is set to true. In that case, both http and https endpoints are stared.</li>
1520   * <li>All the above configurations are overridden by dfs.http.policy
1521   * configuration. With this configuration you can set http-only, https-only
1522   * and http-and-https endpoints.</li>
1523   * </ol>
1524   * See hdfs-default.xml documentation for more details on each of the above
1525   * configuration settings.
1526   */
1527  public static HttpConfig.Policy getHttpPolicy(Configuration conf) {
1528    String policyStr = conf.get(DFSConfigKeys.DFS_HTTP_POLICY_KEY);
1529    if (policyStr == null) {
1530      boolean https = conf.getBoolean(DFSConfigKeys.DFS_HTTPS_ENABLE_KEY,
1531          DFSConfigKeys.DFS_HTTPS_ENABLE_DEFAULT);
1532
1533      boolean hadoopSsl = conf.getBoolean(
1534          CommonConfigurationKeys.HADOOP_SSL_ENABLED_KEY,
1535          CommonConfigurationKeys.HADOOP_SSL_ENABLED_DEFAULT);
1536
1537      if (hadoopSsl) {
1538        LOG.warn(CommonConfigurationKeys.HADOOP_SSL_ENABLED_KEY
1539            + " is deprecated. Please use " + DFSConfigKeys.DFS_HTTP_POLICY_KEY
1540            + ".");
1541      }
1542      if (https) {
1543        LOG.warn(DFSConfigKeys.DFS_HTTPS_ENABLE_KEY
1544            + " is deprecated. Please use " + DFSConfigKeys.DFS_HTTP_POLICY_KEY
1545            + ".");
1546      }
1547
1548      return (hadoopSsl || https) ? HttpConfig.Policy.HTTP_AND_HTTPS
1549          : HttpConfig.Policy.HTTP_ONLY;
1550    }
1551
1552    HttpConfig.Policy policy = HttpConfig.Policy.fromString(policyStr);
1553    if (policy == null) {
1554      throw new HadoopIllegalArgumentException("Unregonized value '"
1555          + policyStr + "' for " + DFSConfigKeys.DFS_HTTP_POLICY_KEY);
1556    }
1557
1558    conf.set(DFSConfigKeys.DFS_HTTP_POLICY_KEY, policy.name());
1559    return policy;
1560  }
1561
1562  public static HttpServer2.Builder loadSslConfToHttpServerBuilder(HttpServer2.Builder builder,
1563      Configuration sslConf) {
1564    return builder
1565        .needsClientAuth(
1566            sslConf.getBoolean(DFS_CLIENT_HTTPS_NEED_AUTH_KEY,
1567                DFS_CLIENT_HTTPS_NEED_AUTH_DEFAULT))
1568        .keyPassword(sslConf.get("ssl.server.keystore.keypassword"))
1569        .keyStore(sslConf.get("ssl.server.keystore.location"),
1570            sslConf.get("ssl.server.keystore.password"),
1571            sslConf.get("ssl.server.keystore.type", "jks"))
1572        .trustStore(sslConf.get("ssl.server.truststore.location"),
1573            sslConf.get("ssl.server.truststore.password"),
1574            sslConf.get("ssl.server.truststore.type", "jks"));
1575  }
1576
1577  /**
1578   * Load HTTPS-related configuration.
1579   */
1580  public static Configuration loadSslConfiguration(Configuration conf) {
1581    Configuration sslConf = new Configuration(false);
1582
1583    sslConf.addResource(conf.get(
1584        DFSConfigKeys.DFS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY,
1585        DFSConfigKeys.DFS_SERVER_HTTPS_KEYSTORE_RESOURCE_DEFAULT));
1586
1587    boolean requireClientAuth = conf.getBoolean(DFS_CLIENT_HTTPS_NEED_AUTH_KEY,
1588        DFS_CLIENT_HTTPS_NEED_AUTH_DEFAULT);
1589    sslConf.setBoolean(DFS_CLIENT_HTTPS_NEED_AUTH_KEY, requireClientAuth);
1590    return sslConf;
1591  }
1592
1593  /**
1594   * Return a HttpServer.Builder that the journalnode / namenode / secondary
1595   * namenode can use to initialize their HTTP / HTTPS server.
1596   *
1597   */
1598  public static HttpServer2.Builder httpServerTemplateForNNAndJN(
1599      Configuration conf, final InetSocketAddress httpAddr,
1600      final InetSocketAddress httpsAddr, String name, String spnegoUserNameKey,
1601      String spnegoKeytabFileKey) throws IOException {
1602    HttpConfig.Policy policy = getHttpPolicy(conf);
1603
1604    HttpServer2.Builder builder = new HttpServer2.Builder().setName(name)
1605        .setConf(conf).setACL(new AccessControlList(conf.get(DFS_ADMIN, " ")))
1606        .setSecurityEnabled(UserGroupInformation.isSecurityEnabled())
1607        .setUsernameConfKey(spnegoUserNameKey)
1608        .setKeytabConfKey(getSpnegoKeytabKey(conf, spnegoKeytabFileKey));
1609
1610    // initialize the webserver for uploading/downloading files.
1611    LOG.info("Starting web server as: "
1612        + SecurityUtil.getServerPrincipal(conf.get(spnegoUserNameKey),
1613            httpAddr.getHostName()));
1614
1615    if (policy.isHttpEnabled()) {
1616      if (httpAddr.getPort() == 0) {
1617        builder.setFindPort(true);
1618      }
1619
1620      URI uri = URI.create("http://" + NetUtils.getHostPortString(httpAddr));
1621      builder.addEndpoint(uri);
1622      LOG.info("Starting Web-server for " + name + " at: " + uri);
1623    }
1624
1625    if (policy.isHttpsEnabled() && httpsAddr != null) {
1626      Configuration sslConf = loadSslConfiguration(conf);
1627      loadSslConfToHttpServerBuilder(builder, sslConf);
1628
1629      if (httpsAddr.getPort() == 0) {
1630        builder.setFindPort(true);
1631      }
1632
1633      URI uri = URI.create("https://" + NetUtils.getHostPortString(httpsAddr));
1634      builder.addEndpoint(uri);
1635      LOG.info("Starting Web-server for " + name + " at: " + uri);
1636    }
1637    return builder;
1638  }
1639
1640  /**
1641   * Converts a Date into an ISO-8601 formatted datetime string.
1642   */
1643  public static String dateToIso8601String(Date date) {
1644    SimpleDateFormat df =
1645        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ENGLISH);
1646    return df.format(date);
1647  }
1648
1649  /**
1650   * Converts a time duration in milliseconds into DDD:HH:MM:SS format.
1651   */
1652  public static String durationToString(long durationMs) {
1653    boolean negative = false;
1654    if (durationMs < 0) {
1655      negative = true;
1656      durationMs = -durationMs;
1657    }
1658    // Chop off the milliseconds
1659    long durationSec = durationMs / 1000;
1660    final int secondsPerMinute = 60;
1661    final int secondsPerHour = 60*60;
1662    final int secondsPerDay = 60*60*24;
1663    final long days = durationSec / secondsPerDay;
1664    durationSec -= days * secondsPerDay;
1665    final long hours = durationSec / secondsPerHour;
1666    durationSec -= hours * secondsPerHour;
1667    final long minutes = durationSec / secondsPerMinute;
1668    durationSec -= minutes * secondsPerMinute;
1669    final long seconds = durationSec;
1670    final long milliseconds = durationMs % 1000;
1671    String format = "%03d:%02d:%02d:%02d.%03d";
1672    if (negative)  {
1673      format = "-" + format;
1674    }
1675    return String.format(format, days, hours, minutes, seconds, milliseconds);
1676  }
1677
1678  /**
1679   * Converts a relative time string into a duration in milliseconds.
1680   */
1681  public static long parseRelativeTime(String relTime) throws IOException {
1682    if (relTime.length() < 2) {
1683      throw new IOException("Unable to parse relative time value of " + relTime
1684          + ": too short");
1685    }
1686    String ttlString = relTime.substring(0, relTime.length()-1);
1687    long ttl;
1688    try {
1689      ttl = Long.parseLong(ttlString);
1690    } catch (NumberFormatException e) {
1691      throw new IOException("Unable to parse relative time value of " + relTime
1692          + ": " + ttlString + " is not a number");
1693    }
1694    if (relTime.endsWith("s")) {
1695      // pass
1696    } else if (relTime.endsWith("m")) {
1697      ttl *= 60;
1698    } else if (relTime.endsWith("h")) {
1699      ttl *= 60*60;
1700    } else if (relTime.endsWith("d")) {
1701      ttl *= 60*60*24;
1702    } else {
1703      throw new IOException("Unable to parse relative time value of " + relTime
1704          + ": unknown time unit " + relTime.charAt(relTime.length() - 1));
1705    }
1706    return ttl*1000;
1707  }
1708
1709  /**
1710   * Assert that all objects in the collection are equal. Returns silently if
1711   * so, throws an AssertionError if any object is not equal. All null values
1712   * are considered equal.
1713   * 
1714   * @param objects the collection of objects to check for equality.
1715   */
1716  public static void assertAllResultsEqual(Collection<?> objects)
1717      throws AssertionError {
1718    if (objects.size() == 0 || objects.size() == 1)
1719      return;
1720    
1721    Object[] resultsArray = objects.toArray();
1722    for (int i = 1; i < resultsArray.length; i++) {
1723      Object currElement = resultsArray[i];
1724      Object lastElement = resultsArray[i - 1];
1725      if ((currElement == null && currElement != lastElement) ||
1726          (currElement != null && !currElement.equals(lastElement))) {
1727        throw new AssertionError("Not all elements match in results: " +
1728          Arrays.toString(resultsArray));
1729      }
1730    }
1731  }
1732}