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.protocol.ClientProtocol;
041 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
042 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSelector;
043 import org.apache.hadoop.hdfs.server.namenode.NameNode;
044 import org.apache.hadoop.io.Text;
045 import org.apache.hadoop.ipc.RPC;
046 import org.apache.hadoop.ipc.RemoteException;
047 import org.apache.hadoop.ipc.StandbyException;
048 import org.apache.hadoop.security.SecurityUtil;
049 import org.apache.hadoop.security.UserGroupInformation;
050 import org.apache.hadoop.security.token.Token;
051
052 import com.google.common.base.Joiner;
053 import com.google.common.base.Preconditions;
054 import com.google.common.collect.Lists;
055
056 public 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 }