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 }