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 }