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    package org.apache.hadoop.hdfs;
019    
020    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_PROXY_PROVIDER_KEY_PREFIX;
021    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_HA_NAMENODE_ID_KEY;
022    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY;
023    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SHARED_EDITS_DIR_KEY;
024    import static org.apache.hadoop.hdfs.protocol.HdfsConstants.HA_DT_SERVICE_PREFIX;
025    
026    import java.io.IOException;
027    import java.net.InetSocketAddress;
028    import java.net.URI;
029    import java.util.ArrayList;
030    import java.util.Collection;
031    import java.util.List;
032    import java.util.Map;
033    
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.apache.hadoop.HadoopIllegalArgumentException;
037    import org.apache.hadoop.conf.Configuration;
038    import org.apache.hadoop.fs.FileSystem;
039    import org.apache.hadoop.fs.Path;
040    import org.apache.hadoop.hdfs.NameNodeProxies.ProxyAndInfo;
041    import org.apache.hadoop.hdfs.protocol.ClientProtocol;
042    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
043    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
044    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSelector;
045    import org.apache.hadoop.hdfs.server.namenode.NameNode;
046    import org.apache.hadoop.hdfs.server.namenode.ha.AbstractNNFailoverProxyProvider;
047    import org.apache.hadoop.io.Text;
048    import org.apache.hadoop.ipc.RPC;
049    import org.apache.hadoop.ipc.RemoteException;
050    import org.apache.hadoop.ipc.StandbyException;
051    import org.apache.hadoop.security.SecurityUtil;
052    import org.apache.hadoop.security.UserGroupInformation;
053    import org.apache.hadoop.security.token.Token;
054    
055    import com.google.common.base.Joiner;
056    import com.google.common.base.Preconditions;
057    import com.google.common.collect.Lists;
058    
059    public 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    }