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