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 }