001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs;
019
020import static org.apache.hadoop.hdfs.DFSConfigKeys.*;
021import java.io.IOException;
022import java.net.InetSocketAddress;
023import java.net.URI;
024import java.net.URISyntaxException;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Map;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.apache.hadoop.HadoopIllegalArgumentException;
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.fs.FileSystem;
034import org.apache.hadoop.fs.Path;
035import org.apache.hadoop.hdfs.protocol.HdfsConstants;
036import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
037import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSelector;
038import org.apache.hadoop.hdfs.server.namenode.NameNode;
039import org.apache.hadoop.io.Text;
040import org.apache.hadoop.ipc.RPC;
041import org.apache.hadoop.security.SecurityUtil;
042import org.apache.hadoop.security.UserGroupInformation;
043import org.apache.hadoop.security.token.Token;
044import static org.apache.hadoop.hdfs.protocol.HdfsConstants.HA_DT_SERVICE_PREFIX;
045
046import com.google.common.base.Joiner;
047import com.google.common.base.Preconditions;
048import com.google.common.collect.Lists;
049
050public class HAUtil {
051  
052  private static final Log LOG = 
053    LogFactory.getLog(HAUtil.class);
054  
055  private static final DelegationTokenSelector tokenSelector =
056      new DelegationTokenSelector();
057
058  private HAUtil() { /* Hidden constructor */ }
059
060  /**
061   * Returns true if HA for namenode is configured for the given nameservice
062   * 
063   * @param conf Configuration
064   * @param nsId nameservice, or null if no federated NS is configured
065   * @return true if HA is configured in the configuration; else false.
066   */
067  public static boolean isHAEnabled(Configuration conf, String nsId) {
068    Map<String, Map<String, InetSocketAddress>> addresses =
069      DFSUtil.getHaNnRpcAddresses(conf);
070    if (addresses == null) return false;
071    Map<String, InetSocketAddress> nnMap = addresses.get(nsId);
072    return nnMap != null && nnMap.size() > 1;
073  }
074
075  /**
076   * Returns true if HA is using a shared edits directory.
077   *
078   * @param conf Configuration
079   * @return true if HA config is using a shared edits dir, false otherwise.
080   */
081  public static boolean usesSharedEditsDir(Configuration conf) {
082    return null != conf.get(DFS_NAMENODE_SHARED_EDITS_DIR_KEY);
083  }
084
085  /**
086   * Get the namenode Id by matching the {@code addressKey}
087   * with the the address of the local node.
088   * 
089   * If {@link DFSConfigKeys#DFS_HA_NAMENODE_ID_KEY} is not specifically
090   * configured, this method determines the namenode Id by matching the local
091   * node's address with the configured addresses. When a match is found, it
092   * returns the namenode Id from the corresponding configuration key.
093   * 
094   * @param conf Configuration
095   * @return namenode Id on success, null on failure.
096   * @throws HadoopIllegalArgumentException on error
097   */
098  public static String getNameNodeId(Configuration conf, String nsId) {
099    String namenodeId = conf.getTrimmed(DFS_HA_NAMENODE_ID_KEY);
100    if (namenodeId != null) {
101      return namenodeId;
102    }
103    
104    String suffixes[] = DFSUtil.getSuffixIDs(conf, DFS_NAMENODE_RPC_ADDRESS_KEY,
105        nsId, null, DFSUtil.LOCAL_ADDRESS_MATCHER);
106    if (suffixes == null) {
107      String msg = "Configuration " + DFS_NAMENODE_RPC_ADDRESS_KEY + 
108          " must be suffixed with nameservice and namenode ID for HA " +
109          "configuration.";
110      throw new HadoopIllegalArgumentException(msg);
111    }
112    
113    return suffixes[1];
114  }
115
116  /**
117   * Similar to
118   * {@link DFSUtil#getNameServiceIdFromAddress(Configuration, 
119   * InetSocketAddress, String...)}
120   */
121  public static String getNameNodeIdFromAddress(final Configuration conf, 
122      final InetSocketAddress address, String... keys) {
123    // Configuration with a single namenode and no nameserviceId
124    String[] ids = DFSUtil.getSuffixIDs(conf, address, keys);
125    if (ids != null && ids.length > 1) {
126      return ids[1];
127    }
128    return null;
129  }
130  
131  /**
132   * Get the NN ID of the other node in an HA setup.
133   * 
134   * @param conf the configuration of this node
135   * @return the NN ID of the other node in this nameservice
136   */
137  public static String getNameNodeIdOfOtherNode(Configuration conf, String nsId) {
138    Preconditions.checkArgument(nsId != null,
139        "Could not determine namespace id. Please ensure that this " +
140        "machine is one of the machines listed as a NN RPC address, " +
141        "or configure " + DFSConfigKeys.DFS_NAMESERVICE_ID);
142    
143    Collection<String> nnIds = DFSUtil.getNameNodeIds(conf, nsId);
144    String myNNId = conf.get(DFSConfigKeys.DFS_HA_NAMENODE_ID_KEY);
145    Preconditions.checkArgument(nnIds != null,
146        "Could not determine namenode ids in namespace '%s'. " +
147        "Please configure " +
148        DFSUtil.addKeySuffixes(DFSConfigKeys.DFS_HA_NAMENODES_KEY_PREFIX,
149            nsId),
150        nsId);
151    Preconditions.checkArgument(nnIds.size() == 2,
152        "Expected exactly 2 NameNodes in namespace '%s'. " +
153        "Instead, got only %s (NN ids were '%s'",
154        nsId, nnIds.size(), Joiner.on("','").join(nnIds));
155    Preconditions.checkState(myNNId != null && !myNNId.isEmpty(),
156        "Could not determine own NN ID in namespace '%s'. Please " +
157        "ensure that this node is one of the machines listed as an " +
158        "NN RPC address, or configure " + DFSConfigKeys.DFS_HA_NAMENODE_ID_KEY,
159        nsId);
160
161    ArrayList<String> nnSet = Lists.newArrayList(nnIds);
162    nnSet.remove(myNNId);
163    assert nnSet.size() == 1;
164    return nnSet.get(0);
165  }
166
167  /**
168   * Given the configuration for this node, return a Configuration object for
169   * the other node in an HA setup.
170   * 
171   * @param myConf the configuration of this node
172   * @return the configuration of the other node in an HA setup
173   */
174  public static Configuration getConfForOtherNode(
175      Configuration myConf) {
176    
177    String nsId = DFSUtil.getNamenodeNameServiceId(myConf);
178    String otherNn = getNameNodeIdOfOtherNode(myConf, nsId);
179    
180    // Look up the address of the active NN.
181    Configuration confForOtherNode = new Configuration(myConf);
182    NameNode.initializeGenericKeys(confForOtherNode, nsId, otherNn);
183    return confForOtherNode;
184  }
185
186  /**
187   * This is used only by tests at the moment.
188   * @return true if the NN should allow read operations while in standby mode.
189   */
190  public static boolean shouldAllowStandbyReads(Configuration conf) {
191    return conf.getBoolean("dfs.ha.allow.stale.reads", false);
192  }
193  
194  public static void setAllowStandbyReads(Configuration conf, boolean val) {
195    conf.setBoolean("dfs.ha.allow.stale.reads", val);
196  }
197 
198  /**
199   * @return true if the given nameNodeUri appears to be a logical URI.
200   * This is the case if there is a failover proxy provider configured
201   * for it in the given configuration.
202   */
203  public static boolean isLogicalUri(
204      Configuration conf, URI nameNodeUri) {
205    String host = nameNodeUri.getHost();
206    String configKey = DFS_CLIENT_FAILOVER_PROXY_PROVIDER_KEY_PREFIX + "."
207        + host;
208    return conf.get(configKey) != null;
209  }
210
211  /**
212   * Parse the HDFS URI out of the provided token.
213   * @throws IOException if the token is invalid
214   */
215  public static URI getServiceUriFromToken(
216      Token<DelegationTokenIdentifier> token)
217      throws IOException {
218    String tokStr = token.getService().toString();
219
220    if (tokStr.startsWith(HA_DT_SERVICE_PREFIX)) {
221      tokStr = tokStr.replaceFirst(HA_DT_SERVICE_PREFIX, "");
222    }
223    
224    try {
225      return new URI(HdfsConstants.HDFS_URI_SCHEME + "://" +
226          tokStr);
227    } catch (URISyntaxException e) {
228      throw new IOException("Invalid token contents: '" +
229          tokStr + "'");
230    }
231  }
232  
233  /**
234   * Get the service name used in the delegation token for the given logical
235   * HA service.
236   * @param uri the logical URI of the cluster
237   * @return the service name
238   */
239  public static Text buildTokenServiceForLogicalUri(URI uri) {
240    return new Text(HA_DT_SERVICE_PREFIX + uri.getHost());
241  }
242  
243  /**
244   * @return true if this token corresponds to a logical nameservice
245   * rather than a specific namenode.
246   */
247  public static boolean isTokenForLogicalUri(
248      Token<DelegationTokenIdentifier> token) {
249    return token.getService().toString().startsWith(HA_DT_SERVICE_PREFIX);
250  }
251  
252  /**
253   * Locate a delegation token associated with the given HA cluster URI, and if
254   * one is found, clone it to also represent the underlying namenode address.
255   * @param ugi the UGI to modify
256   * @param haUri the logical URI for the cluster
257   * @param nnAddrs collection of NNs in the cluster to which the token
258   * applies
259   */
260  public static void cloneDelegationTokenForLogicalUri(
261      UserGroupInformation ugi, URI haUri,
262      Collection<InetSocketAddress> nnAddrs) {
263    Text haService = HAUtil.buildTokenServiceForLogicalUri(haUri);
264    Token<DelegationTokenIdentifier> haToken =
265        tokenSelector.selectToken(haService, ugi.getTokens());
266    if (haToken != null) {
267      for (InetSocketAddress singleNNAddr : nnAddrs) {
268        Token<DelegationTokenIdentifier> specificToken =
269            new Token<DelegationTokenIdentifier>(haToken);
270        SecurityUtil.setTokenService(specificToken, singleNNAddr);
271        ugi.addToken(specificToken);
272        LOG.debug("Mapped HA service delegation token for logical URI " +
273            haUri + " to namenode " + singleNNAddr);
274      }
275    } else {
276      LOG.debug("No HA service delegation token found for logical URI " +
277          haUri);
278    }
279  }
280
281  /**
282   * Get the internet address of the currently-active NN. This should rarely be
283   * used, since callers of this method who connect directly to the NN using the
284   * resulting InetSocketAddress will not be able to connect to the active NN if
285   * a failover were to occur after this method has been called.
286   * 
287   * @param fs the file system to get the active address of.
288   * @return the internet address of the currently-active NN.
289   * @throws IOException if an error occurs while resolving the active NN.
290   */
291  @SuppressWarnings("deprecation")
292  public static InetSocketAddress getAddressOfActive(FileSystem fs)
293      throws IOException {
294    if (!(fs instanceof DistributedFileSystem)) {
295      throw new IllegalArgumentException("FileSystem " + fs + " is not a DFS.");
296    }
297    // force client address resolution.
298    fs.exists(new Path("/"));
299    DistributedFileSystem dfs = (DistributedFileSystem) fs;
300    DFSClient dfsClient = dfs.getClient();
301    return RPC.getServerAddress(dfsClient.getNamenode());
302  }
303}