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