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.DFS_CLIENT_FAILOVER_PROXY_PROVIDER_KEY_PREFIX;
021import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_HA_NAMENODE_ID_KEY;
022import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY;
023import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SHARED_EDITS_DIR_KEY;
024import static org.apache.hadoop.hdfs.protocol.HdfsConstants.HA_DT_SERVICE_PREFIX;
025
026import java.io.IOException;
027import java.net.InetSocketAddress;
028import java.net.URI;
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.List;
032import java.util.Map;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.apache.hadoop.HadoopIllegalArgumentException;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.fs.FileSystem;
039import org.apache.hadoop.fs.Path;
040import org.apache.hadoop.hdfs.NameNodeProxies.ProxyAndInfo;
041import org.apache.hadoop.hdfs.protocol.ClientProtocol;
042import org.apache.hadoop.hdfs.protocol.HdfsConstants;
043import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
044import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSelector;
045import org.apache.hadoop.hdfs.server.namenode.NameNode;
046import org.apache.hadoop.hdfs.server.namenode.ha.AbstractNNFailoverProxyProvider;
047import org.apache.hadoop.io.Text;
048import org.apache.hadoop.ipc.RPC;
049import org.apache.hadoop.ipc.RemoteException;
050import org.apache.hadoop.ipc.StandbyException;
051import org.apache.hadoop.security.SecurityUtil;
052import org.apache.hadoop.security.UserGroupInformation;
053import org.apache.hadoop.security.token.Token;
054
055import com.google.common.base.Joiner;
056import com.google.common.base.Preconditions;
057import com.google.common.collect.Lists;
058
059public class HAUtil {
060  
061  private static final Log LOG = 
062    LogFactory.getLog(HAUtil.class);
063  
064  private static final DelegationTokenSelector tokenSelector =
065      new DelegationTokenSelector();
066
067  private HAUtil() { /* Hidden constructor */ }
068
069  /**
070   * Returns true if HA for namenode is configured for the given nameservice
071   * 
072   * @param conf Configuration
073   * @param nsId nameservice, or null if no federated NS is configured
074   * @return true if HA is configured in the configuration; else false.
075   */
076  public static boolean isHAEnabled(Configuration conf, String nsId) {
077    Map<String, Map<String, InetSocketAddress>> addresses =
078      DFSUtil.getHaNnRpcAddresses(conf);
079    if (addresses == null) return false;
080    Map<String, InetSocketAddress> nnMap = addresses.get(nsId);
081    return nnMap != null && nnMap.size() > 1;
082  }
083
084  /**
085   * Returns true if HA is using a shared edits directory.
086   *
087   * @param conf Configuration
088   * @return true if HA config is using a shared edits dir, false otherwise.
089   */
090  public static boolean usesSharedEditsDir(Configuration conf) {
091    return null != conf.get(DFS_NAMENODE_SHARED_EDITS_DIR_KEY);
092  }
093
094  /**
095   * Get the namenode Id by matching the {@code addressKey}
096   * with the the address of the local node.
097   * 
098   * If {@link DFSConfigKeys#DFS_HA_NAMENODE_ID_KEY} is not specifically
099   * configured, this method determines the namenode Id by matching the local
100   * node's address with the configured addresses. When a match is found, it
101   * returns the namenode Id from the corresponding configuration key.
102   * 
103   * @param conf Configuration
104   * @return namenode Id on success, null on failure.
105   * @throws HadoopIllegalArgumentException on error
106   */
107  public static String getNameNodeId(Configuration conf, String nsId) {
108    String namenodeId = conf.getTrimmed(DFS_HA_NAMENODE_ID_KEY);
109    if (namenodeId != null) {
110      return namenodeId;
111    }
112    
113    String suffixes[] = DFSUtil.getSuffixIDs(conf, DFS_NAMENODE_RPC_ADDRESS_KEY,
114        nsId, null, DFSUtil.LOCAL_ADDRESS_MATCHER);
115    if (suffixes == null) {
116      String msg = "Configuration " + DFS_NAMENODE_RPC_ADDRESS_KEY + 
117          " must be suffixed with nameservice and namenode ID for HA " +
118          "configuration.";
119      throw new HadoopIllegalArgumentException(msg);
120    }
121    
122    return suffixes[1];
123  }
124
125  /**
126   * Similar to
127   * {@link DFSUtil#getNameServiceIdFromAddress(Configuration, 
128   * InetSocketAddress, String...)}
129   */
130  public static String getNameNodeIdFromAddress(final Configuration conf, 
131      final InetSocketAddress address, String... keys) {
132    // Configuration with a single namenode and no nameserviceId
133    String[] ids = DFSUtil.getSuffixIDs(conf, address, keys);
134    if (ids != null && ids.length > 1) {
135      return ids[1];
136    }
137    return null;
138  }
139  
140  /**
141   * Get the NN ID of the other node in an HA setup.
142   * 
143   * @param conf the configuration of this node
144   * @return the NN ID of the other node in this nameservice
145   */
146  public static String getNameNodeIdOfOtherNode(Configuration conf, String nsId) {
147    Preconditions.checkArgument(nsId != null,
148        "Could not determine namespace id. Please ensure that this " +
149        "machine is one of the machines listed as a NN RPC address, " +
150        "or configure " + DFSConfigKeys.DFS_NAMESERVICE_ID);
151    
152    Collection<String> nnIds = DFSUtil.getNameNodeIds(conf, nsId);
153    String myNNId = conf.get(DFSConfigKeys.DFS_HA_NAMENODE_ID_KEY);
154    Preconditions.checkArgument(nnIds != null,
155        "Could not determine namenode ids in namespace '%s'. " +
156        "Please configure " +
157        DFSUtil.addKeySuffixes(DFSConfigKeys.DFS_HA_NAMENODES_KEY_PREFIX,
158            nsId),
159        nsId);
160    Preconditions.checkArgument(nnIds.size() == 2,
161        "Expected exactly 2 NameNodes in namespace '%s'. " +
162        "Instead, got only %s (NN ids were '%s'",
163        nsId, nnIds.size(), Joiner.on("','").join(nnIds));
164    Preconditions.checkState(myNNId != null && !myNNId.isEmpty(),
165        "Could not determine own NN ID in namespace '%s'. Please " +
166        "ensure that this node is one of the machines listed as an " +
167        "NN RPC address, or configure " + DFSConfigKeys.DFS_HA_NAMENODE_ID_KEY,
168        nsId);
169
170    ArrayList<String> nnSet = Lists.newArrayList(nnIds);
171    nnSet.remove(myNNId);
172    assert nnSet.size() == 1;
173    return nnSet.get(0);
174  }
175
176  /**
177   * Given the configuration for this node, return a Configuration object for
178   * the other node in an HA setup.
179   * 
180   * @param myConf the configuration of this node
181   * @return the configuration of the other node in an HA setup
182   */
183  public static Configuration getConfForOtherNode(
184      Configuration myConf) {
185    
186    String nsId = DFSUtil.getNamenodeNameServiceId(myConf);
187    String otherNn = getNameNodeIdOfOtherNode(myConf, nsId);
188    
189    // Look up the address of the active NN.
190    Configuration confForOtherNode = new Configuration(myConf);
191    NameNode.initializeGenericKeys(confForOtherNode, nsId, otherNn);
192    return confForOtherNode;
193  }
194
195  /**
196   * This is used only by tests at the moment.
197   * @return true if the NN should allow read operations while in standby mode.
198   */
199  public static boolean shouldAllowStandbyReads(Configuration conf) {
200    return conf.getBoolean("dfs.ha.allow.stale.reads", false);
201  }
202  
203  public static void setAllowStandbyReads(Configuration conf, boolean val) {
204    conf.setBoolean("dfs.ha.allow.stale.reads", val);
205  }
206 
207  /**
208   * @return true if the given nameNodeUri appears to be a logical URI.
209   */
210  public static boolean isLogicalUri(
211      Configuration conf, URI nameNodeUri) {
212    String host = nameNodeUri.getHost();
213    // A logical name must be one of the service IDs.
214    return DFSUtil.getNameServiceIds(conf).contains(host);
215  }
216
217  /**
218   * Check whether the client has a failover proxy provider configured
219   * for the namenode/nameservice.
220   *
221   * @param conf Configuration
222   * @param nameNodeUri The URI of namenode
223   * @return true if failover is configured.
224   */
225  public static boolean isClientFailoverConfigured(
226      Configuration conf, URI nameNodeUri) {
227    String host = nameNodeUri.getHost();
228    String configKey = DFS_CLIENT_FAILOVER_PROXY_PROVIDER_KEY_PREFIX + "."
229        + host;
230    return conf.get(configKey) != null;
231  }
232
233  /**
234   * Check whether logical URI is needed for the namenode and
235   * the corresponding failover proxy provider in the config.
236   *
237   * @param conf Configuration
238   * @param nameNodeUri The URI of namenode
239   * @return true if logical URI is needed. false, if not needed.
240   * @throws IOException most likely due to misconfiguration.
241   */
242  public static boolean useLogicalUri(Configuration conf, URI nameNodeUri) 
243      throws IOException {
244    // Create the proxy provider. Actual proxy is not created.
245    AbstractNNFailoverProxyProvider<ClientProtocol> provider = NameNodeProxies
246        .createFailoverProxyProvider(conf, nameNodeUri, ClientProtocol.class,
247        false, null);
248
249    // No need to use logical URI since failover is not configured.
250    if (provider == null) {
251      return false;
252    }
253    // Check whether the failover proxy provider uses logical URI.
254    return provider.useLogicalURI();
255  }
256
257  /**
258   * Parse the file system URI out of the provided token.
259   */
260  public static URI getServiceUriFromToken(final String scheme, Token<?> token) {
261    String tokStr = token.getService().toString();
262    final String prefix = buildTokenServicePrefixForLogicalUri(scheme);
263    if (tokStr.startsWith(prefix)) {
264      tokStr = tokStr.replaceFirst(prefix, "");
265    }
266    return URI.create(scheme + "://" + tokStr);
267  }
268  
269  /**
270   * Get the service name used in the delegation token for the given logical
271   * HA service.
272   * @param uri the logical URI of the cluster
273   * @param scheme the scheme of the corresponding FileSystem
274   * @return the service name
275   */
276  public static Text buildTokenServiceForLogicalUri(final URI uri,
277      final String scheme) {
278    return new Text(buildTokenServicePrefixForLogicalUri(scheme)
279        + uri.getHost());
280  }
281  
282  /**
283   * @return true if this token corresponds to a logical nameservice
284   * rather than a specific namenode.
285   */
286  public static boolean isTokenForLogicalUri(Token<?> token) {
287    return token.getService().toString().startsWith(HA_DT_SERVICE_PREFIX);
288  }
289
290  public static String buildTokenServicePrefixForLogicalUri(String scheme) {
291    return HA_DT_SERVICE_PREFIX + scheme + ":";
292  }
293
294  /**
295   * Locate a delegation token associated with the given HA cluster URI, and if
296   * one is found, clone it to also represent the underlying namenode address.
297   * @param ugi the UGI to modify
298   * @param haUri the logical URI for the cluster
299   * @param nnAddrs collection of NNs in the cluster to which the token
300   * applies
301   */
302  public static void cloneDelegationTokenForLogicalUri(
303      UserGroupInformation ugi, URI haUri,
304      Collection<InetSocketAddress> nnAddrs) {
305    // this cloning logic is only used by hdfs
306    Text haService = HAUtil.buildTokenServiceForLogicalUri(haUri,
307        HdfsConstants.HDFS_URI_SCHEME);
308    Token<DelegationTokenIdentifier> haToken =
309        tokenSelector.selectToken(haService, ugi.getTokens());
310    if (haToken != null) {
311      for (InetSocketAddress singleNNAddr : nnAddrs) {
312        // this is a minor hack to prevent physical HA tokens from being
313        // exposed to the user via UGI.getCredentials(), otherwise these
314        // cloned tokens may be inadvertently propagated to jobs
315        Token<DelegationTokenIdentifier> specificToken =
316            new Token.PrivateToken<DelegationTokenIdentifier>(haToken);
317        SecurityUtil.setTokenService(specificToken, singleNNAddr);
318        Text alias = new Text(
319            buildTokenServicePrefixForLogicalUri(HdfsConstants.HDFS_URI_SCHEME)
320                + "//" + specificToken.getService());
321        ugi.addToken(alias, specificToken);
322        LOG.debug("Mapped HA service delegation token for logical URI " +
323            haUri + " to namenode " + singleNNAddr);
324      }
325    } else {
326      LOG.debug("No HA service delegation token found for logical URI " +
327          haUri);
328    }
329  }
330
331  /**
332   * Get the internet address of the currently-active NN. This should rarely be
333   * used, since callers of this method who connect directly to the NN using the
334   * resulting InetSocketAddress will not be able to connect to the active NN if
335   * a failover were to occur after this method has been called.
336   * 
337   * @param fs the file system to get the active address of.
338   * @return the internet address of the currently-active NN.
339   * @throws IOException if an error occurs while resolving the active NN.
340   */
341  public static InetSocketAddress getAddressOfActive(FileSystem fs)
342      throws IOException {
343    if (!(fs instanceof DistributedFileSystem)) {
344      throw new IllegalArgumentException("FileSystem " + fs + " is not a DFS.");
345    }
346    // force client address resolution.
347    fs.exists(new Path("/"));
348    DistributedFileSystem dfs = (DistributedFileSystem) fs;
349    DFSClient dfsClient = dfs.getClient();
350    return RPC.getServerAddress(dfsClient.getNamenode());
351  }
352  
353  /**
354   * Get an RPC proxy for each NN in an HA nameservice. Used when a given RPC
355   * call should be made on every NN in an HA nameservice, not just the active.
356   * 
357   * @param conf configuration
358   * @param nsId the nameservice to get all of the proxies for.
359   * @return a list of RPC proxies for each NN in the nameservice.
360   * @throws IOException in the event of error.
361   */
362  public static List<ClientProtocol> getProxiesForAllNameNodesInNameservice(
363      Configuration conf, String nsId) throws IOException {
364    List<ProxyAndInfo<ClientProtocol>> proxies =
365        getProxiesForAllNameNodesInNameservice(conf, nsId, ClientProtocol.class);
366
367    List<ClientProtocol> namenodes = new ArrayList<ClientProtocol>(
368        proxies.size());
369    for (ProxyAndInfo<ClientProtocol> proxy : proxies) {
370      namenodes.add(proxy.getProxy());
371    }
372    return namenodes;
373  }
374
375  /**
376   * Get an RPC proxy for each NN in an HA nameservice. Used when a given RPC
377   * call should be made on every NN in an HA nameservice, not just the active.
378   *
379   * @param conf configuration
380   * @param nsId the nameservice to get all of the proxies for.
381   * @param xface the protocol class.
382   * @return a list of RPC proxies for each NN in the nameservice.
383   * @throws IOException in the event of error.
384   */
385  public static <T> List<ProxyAndInfo<T>> getProxiesForAllNameNodesInNameservice(
386      Configuration conf, String nsId, Class<T> xface) throws IOException {
387    Map<String, InetSocketAddress> nnAddresses =
388        DFSUtil.getRpcAddressesForNameserviceId(conf, nsId, null);
389    
390    List<ProxyAndInfo<T>> proxies = new ArrayList<ProxyAndInfo<T>>(
391        nnAddresses.size());
392    for (InetSocketAddress nnAddress : nnAddresses.values()) {
393      NameNodeProxies.ProxyAndInfo<T> proxyInfo = null;
394      proxyInfo = NameNodeProxies.createNonHAProxy(conf,
395          nnAddress, xface,
396          UserGroupInformation.getCurrentUser(), false);
397      proxies.add(proxyInfo);
398    }
399    return proxies;
400  }
401  
402  /**
403   * Used to ensure that at least one of the given HA NNs is currently in the
404   * active state..
405   * 
406   * @param namenodes list of RPC proxies for each NN to check.
407   * @return true if at least one NN is active, false if all are in the standby state.
408   * @throws IOException in the event of error.
409   */
410  public static boolean isAtLeastOneActive(List<ClientProtocol> namenodes)
411      throws IOException {
412    for (ClientProtocol namenode : namenodes) {
413      try {
414        namenode.getFileInfo("/");
415        return true;
416      } catch (RemoteException re) {
417        IOException cause = re.unwrapRemoteException();
418        if (cause instanceof StandbyException) {
419          // This is expected to happen for a standby NN.
420        } else {
421          throw re;
422        }
423      }
424    }
425    return false;
426  }
427}