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 com.google.common.base.Joiner;
021    import com.google.common.base.Preconditions;
022    import com.google.common.collect.Lists;
023    import org.apache.commons.logging.Log;
024    import org.apache.commons.logging.LogFactory;
025    import org.apache.hadoop.HadoopIllegalArgumentException;
026    import org.apache.hadoop.conf.Configuration;
027    import org.apache.hadoop.fs.FileSystem;
028    import org.apache.hadoop.fs.Path;
029    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
030    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
031    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSelector;
032    import org.apache.hadoop.hdfs.server.namenode.NameNode;
033    import org.apache.hadoop.io.Text;
034    import org.apache.hadoop.ipc.RPC;
035    import org.apache.hadoop.security.SecurityUtil;
036    import org.apache.hadoop.security.UserGroupInformation;
037    import org.apache.hadoop.security.token.Token;
038    
039    import java.io.IOException;
040    import java.net.InetSocketAddress;
041    import java.net.URI;
042    import java.net.URISyntaxException;
043    import java.util.ArrayList;
044    import java.util.Collection;
045    import java.util.Map;
046    
047    import static org.apache.hadoop.hdfs.DFSConfigKeys.*;
048    import static org.apache.hadoop.hdfs.protocol.HdfsConstants.HA_DT_SERVICE_PREFIX;
049    
050    public 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            // this is a minor hack to prevent physical HA tokens from being
269            // exposed to the user via UGI.getCredentials(), otherwise these
270            // cloned tokens may be inadvertently propagated to jobs
271            Token<DelegationTokenIdentifier> specificToken =
272                new Token.PrivateToken<DelegationTokenIdentifier>(haToken);
273            SecurityUtil.setTokenService(specificToken, singleNNAddr);
274            Text alias =
275                new Text(HA_DT_SERVICE_PREFIX + "//" + specificToken.getService());
276            ugi.addToken(alias, specificToken);
277            LOG.debug("Mapped HA service delegation token for logical URI " +
278                haUri + " to namenode " + singleNNAddr);
279          }
280        } else {
281          LOG.debug("No HA service delegation token found for logical URI " +
282              haUri);
283        }
284      }
285    
286      /**
287       * Get the internet address of the currently-active NN. This should rarely be
288       * used, since callers of this method who connect directly to the NN using the
289       * resulting InetSocketAddress will not be able to connect to the active NN if
290       * a failover were to occur after this method has been called.
291       * 
292       * @param fs the file system to get the active address of.
293       * @return the internet address of the currently-active NN.
294       * @throws IOException if an error occurs while resolving the active NN.
295       */
296      @SuppressWarnings("deprecation")
297      public static InetSocketAddress getAddressOfActive(FileSystem fs)
298          throws IOException {
299        if (!(fs instanceof DistributedFileSystem)) {
300          throw new IllegalArgumentException("FileSystem " + fs + " is not a DFS.");
301        }
302        // force client address resolution.
303        fs.exists(new Path("/"));
304        DistributedFileSystem dfs = (DistributedFileSystem) fs;
305        DFSClient dfsClient = dfs.getClient();
306        return RPC.getServerAddress(dfsClient.getNamenode());
307      }
308    }