001/** 002res * 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 019package org.apache.hadoop.hdfs.web; 020 021import java.io.BufferedOutputStream; 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.HttpURLConnection; 026import java.net.InetSocketAddress; 027import java.net.MalformedURLException; 028import java.net.URI; 029import java.net.URL; 030import java.security.PrivilegedExceptionAction; 031import java.util.ArrayList; 032import java.util.EnumSet; 033import java.util.List; 034import java.util.Map; 035import java.util.StringTokenizer; 036 037import javax.ws.rs.core.MediaType; 038 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.apache.hadoop.conf.Configuration; 042import org.apache.hadoop.fs.BlockLocation; 043import org.apache.hadoop.fs.CommonConfigurationKeys; 044import org.apache.hadoop.fs.ContentSummary; 045import org.apache.hadoop.fs.DelegationTokenRenewer; 046import org.apache.hadoop.fs.FSDataInputStream; 047import org.apache.hadoop.fs.FSDataOutputStream; 048import org.apache.hadoop.fs.FileStatus; 049import org.apache.hadoop.fs.FileSystem; 050import org.apache.hadoop.fs.MD5MD5CRC32FileChecksum; 051import org.apache.hadoop.fs.Options; 052import org.apache.hadoop.fs.Path; 053import org.apache.hadoop.fs.XAttrCodec; 054import org.apache.hadoop.fs.XAttrSetFlag; 055import org.apache.hadoop.fs.permission.AclEntry; 056import org.apache.hadoop.fs.permission.AclStatus; 057import org.apache.hadoop.fs.permission.FsAction; 058import org.apache.hadoop.fs.permission.FsPermission; 059import org.apache.hadoop.hdfs.DFSConfigKeys; 060import org.apache.hadoop.hdfs.DFSUtil; 061import org.apache.hadoop.hdfs.HAUtil; 062import org.apache.hadoop.hdfs.protocol.HdfsFileStatus; 063import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; 064import org.apache.hadoop.hdfs.server.namenode.SafeModeException; 065import org.apache.hadoop.hdfs.web.resources.*; 066import org.apache.hadoop.hdfs.web.resources.HttpOpParam.Op; 067import org.apache.hadoop.io.Text; 068import org.apache.hadoop.io.retry.RetryPolicies; 069import org.apache.hadoop.io.retry.RetryPolicy; 070import org.apache.hadoop.io.retry.RetryUtils; 071import org.apache.hadoop.ipc.RemoteException; 072import org.apache.hadoop.net.NetUtils; 073import org.apache.hadoop.security.AccessControlException; 074import org.apache.hadoop.security.SecurityUtil; 075import org.apache.hadoop.security.UserGroupInformation; 076import org.apache.hadoop.security.token.SecretManager.InvalidToken; 077import org.apache.hadoop.security.token.Token; 078import org.apache.hadoop.security.token.TokenIdentifier; 079import org.apache.hadoop.security.token.TokenSelector; 080import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSelector; 081import org.apache.hadoop.util.Progressable; 082import org.apache.hadoop.util.StringUtils; 083import org.codehaus.jackson.map.ObjectMapper; 084 085import com.google.common.annotations.VisibleForTesting; 086import com.google.common.base.Preconditions; 087import com.google.common.collect.Lists; 088 089/** A FileSystem for HDFS over the web. */ 090public class WebHdfsFileSystem extends FileSystem 091 implements DelegationTokenRenewer.Renewable, TokenAspect.TokenManagementDelegator { 092 public static final Log LOG = LogFactory.getLog(WebHdfsFileSystem.class); 093 /** File System URI: {SCHEME}://namenode:port/path/to/file */ 094 public static final String SCHEME = "webhdfs"; 095 /** WebHdfs version. */ 096 public static final int VERSION = 1; 097 /** Http URI: http://namenode:port/{PATH_PREFIX}/path/to/file */ 098 public static final String PATH_PREFIX = "/" + SCHEME + "/v" + VERSION; 099 100 /** Default connection factory may be overridden in tests to use smaller timeout values */ 101 protected URLConnectionFactory connectionFactory; 102 103 /** Delegation token kind */ 104 public static final Text TOKEN_KIND = new Text("WEBHDFS delegation"); 105 106 @VisibleForTesting 107 public static final String CANT_FALLBACK_TO_INSECURE_MSG = 108 "The client is configured to only allow connecting to secure cluster"; 109 110 private boolean canRefreshDelegationToken; 111 112 private UserGroupInformation ugi; 113 private URI uri; 114 private Token<?> delegationToken; 115 protected Text tokenServiceName; 116 private RetryPolicy retryPolicy = null; 117 private Path workingDir; 118 private InetSocketAddress nnAddrs[]; 119 private int currentNNAddrIndex; 120 private boolean disallowFallbackToInsecureCluster; 121 122 /** 123 * Return the protocol scheme for the FileSystem. 124 * <p/> 125 * 126 * @return <code>webhdfs</code> 127 */ 128 @Override 129 public String getScheme() { 130 return SCHEME; 131 } 132 133 /** 134 * return the underlying transport protocol (http / https). 135 */ 136 protected String getTransportScheme() { 137 return "http"; 138 } 139 140 protected Text getTokenKind() { 141 return TOKEN_KIND; 142 } 143 144 @Override 145 public synchronized void initialize(URI uri, Configuration conf 146 ) throws IOException { 147 super.initialize(uri, conf); 148 setConf(conf); 149 /** set user pattern based on configuration file */ 150 UserParam.setUserPattern(conf.get( 151 DFSConfigKeys.DFS_WEBHDFS_USER_PATTERN_KEY, 152 DFSConfigKeys.DFS_WEBHDFS_USER_PATTERN_DEFAULT)); 153 154 connectionFactory = URLConnectionFactory 155 .newDefaultURLConnectionFactory(conf); 156 157 ugi = UserGroupInformation.getCurrentUser(); 158 this.uri = URI.create(uri.getScheme() + "://" + uri.getAuthority()); 159 this.nnAddrs = resolveNNAddr(); 160 161 boolean isHA = HAUtil.isClientFailoverConfigured(conf, this.uri); 162 boolean isLogicalUri = isHA && HAUtil.isLogicalUri(conf, this.uri); 163 // In non-HA or non-logical URI case, the code needs to call 164 // getCanonicalUri() in order to handle the case where no port is 165 // specified in the URI 166 this.tokenServiceName = isLogicalUri ? 167 HAUtil.buildTokenServiceForLogicalUri(uri, getScheme()) 168 : SecurityUtil.buildTokenService(getCanonicalUri()); 169 170 if (!isHA) { 171 this.retryPolicy = 172 RetryUtils.getDefaultRetryPolicy( 173 conf, 174 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_ENABLED_KEY, 175 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_ENABLED_DEFAULT, 176 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_SPEC_KEY, 177 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_SPEC_DEFAULT, 178 SafeModeException.class); 179 } else { 180 181 int maxFailoverAttempts = conf.getInt( 182 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_MAX_ATTEMPTS_KEY, 183 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_MAX_ATTEMPTS_DEFAULT); 184 int maxRetryAttempts = conf.getInt( 185 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_MAX_ATTEMPTS_KEY, 186 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_MAX_ATTEMPTS_DEFAULT); 187 int failoverSleepBaseMillis = conf.getInt( 188 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_BASE_KEY, 189 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_BASE_DEFAULT); 190 int failoverSleepMaxMillis = conf.getInt( 191 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_MAX_KEY, 192 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_MAX_DEFAULT); 193 194 this.retryPolicy = RetryPolicies 195 .failoverOnNetworkException(RetryPolicies.TRY_ONCE_THEN_FAIL, 196 maxFailoverAttempts, maxRetryAttempts, failoverSleepBaseMillis, 197 failoverSleepMaxMillis); 198 } 199 200 this.workingDir = getHomeDirectory(); 201 this.canRefreshDelegationToken = UserGroupInformation.isSecurityEnabled(); 202 this.disallowFallbackToInsecureCluster = !conf.getBoolean( 203 CommonConfigurationKeys.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, 204 CommonConfigurationKeys.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_DEFAULT); 205 this.delegationToken = null; 206 } 207 208 @Override 209 public URI getCanonicalUri() { 210 return super.getCanonicalUri(); 211 } 212 213 /** Is WebHDFS enabled in conf? */ 214 public static boolean isEnabled(final Configuration conf, final Log log) { 215 final boolean b = conf.getBoolean(DFSConfigKeys.DFS_WEBHDFS_ENABLED_KEY, 216 DFSConfigKeys.DFS_WEBHDFS_ENABLED_DEFAULT); 217 return b; 218 } 219 220 TokenSelector<DelegationTokenIdentifier> tokenSelector = 221 new AbstractDelegationTokenSelector<DelegationTokenIdentifier>(getTokenKind()){}; 222 223 // the first getAuthParams() for a non-token op will either get the 224 // internal token from the ugi or lazy fetch one 225 protected synchronized Token<?> getDelegationToken() throws IOException { 226 try { 227 if (canRefreshDelegationToken && delegationToken == null) { 228 Token<?> token = tokenSelector.selectToken( 229 new Text(getCanonicalServiceName()), ugi.getTokens()); 230 // ugi tokens are usually indicative of a task which can't 231 // refetch tokens. even if ugi has credentials, don't attempt 232 // to get another token to match hdfs/rpc behavior 233 if (token != null) { 234 LOG.debug("Using UGI token: " + token); 235 canRefreshDelegationToken = false; 236 } else { 237 token = getDelegationToken(null); 238 if (token != null) { 239 LOG.debug("Fetched new token: " + token); 240 } else { // security is disabled 241 canRefreshDelegationToken = false; 242 } 243 } 244 setDelegationToken(token); 245 } 246 return delegationToken; 247 } catch (IOException e) { 248 LOG.warn(e.getMessage()); 249 LOG.debug(e.getMessage(), e); 250 } 251 252 return null; 253 } 254 255 @VisibleForTesting 256 synchronized boolean replaceExpiredDelegationToken() throws IOException { 257 boolean replaced = false; 258 if (canRefreshDelegationToken) { 259 Token<?> token = getDelegationToken(null); 260 LOG.debug("Replaced expired token: " + token); 261 setDelegationToken(token); 262 replaced = (token != null); 263 } 264 return replaced; 265 } 266 267 @Override 268 @VisibleForTesting 269 public int getDefaultPort() { 270 return getConf().getInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY, 271 DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT); 272 } 273 274 @Override 275 public URI getUri() { 276 return this.uri; 277 } 278 279 @Override 280 protected URI canonicalizeUri(URI uri) { 281 return NetUtils.getCanonicalUri(uri, getDefaultPort()); 282 } 283 284 /** @return the home directory. */ 285 public static String getHomeDirectoryString(final UserGroupInformation ugi) { 286 return "/user/" + ugi.getShortUserName(); 287 } 288 289 @Override 290 public Path getHomeDirectory() { 291 return makeQualified(new Path(getHomeDirectoryString(ugi))); 292 } 293 294 @Override 295 public synchronized Path getWorkingDirectory() { 296 return workingDir; 297 } 298 299 @Override 300 public synchronized void setWorkingDirectory(final Path dir) { 301 String result = makeAbsolute(dir).toUri().getPath(); 302 if (!DFSUtil.isValidName(result)) { 303 throw new IllegalArgumentException("Invalid DFS directory name " + 304 result); 305 } 306 workingDir = makeAbsolute(dir); 307 } 308 309 private Path makeAbsolute(Path f) { 310 return f.isAbsolute()? f: new Path(workingDir, f); 311 } 312 313 static Map<?, ?> jsonParse(final HttpURLConnection c, final boolean useErrorStream 314 ) throws IOException { 315 if (c.getContentLength() == 0) { 316 return null; 317 } 318 final InputStream in = useErrorStream? c.getErrorStream(): c.getInputStream(); 319 if (in == null) { 320 throw new IOException("The " + (useErrorStream? "error": "input") + " stream is null."); 321 } 322 try { 323 final String contentType = c.getContentType(); 324 if (contentType != null) { 325 final MediaType parsed = MediaType.valueOf(contentType); 326 if (!MediaType.APPLICATION_JSON_TYPE.isCompatible(parsed)) { 327 throw new IOException("Content-Type \"" + contentType 328 + "\" is incompatible with \"" + MediaType.APPLICATION_JSON 329 + "\" (parsed=\"" + parsed + "\")"); 330 } 331 } 332 ObjectMapper mapper = new ObjectMapper(); 333 return mapper.reader(Map.class).readValue(in); 334 } finally { 335 in.close(); 336 } 337 } 338 339 private static Map<?, ?> validateResponse(final HttpOpParam.Op op, 340 final HttpURLConnection conn, boolean unwrapException) throws IOException { 341 final int code = conn.getResponseCode(); 342 // server is demanding an authentication we don't support 343 if (code == HttpURLConnection.HTTP_UNAUTHORIZED) { 344 // match hdfs/rpc exception 345 throw new AccessControlException(conn.getResponseMessage()); 346 } 347 if (code != op.getExpectedHttpResponseCode()) { 348 final Map<?, ?> m; 349 try { 350 m = jsonParse(conn, true); 351 } catch(Exception e) { 352 throw new IOException("Unexpected HTTP response: code=" + code + " != " 353 + op.getExpectedHttpResponseCode() + ", " + op.toQueryString() 354 + ", message=" + conn.getResponseMessage(), e); 355 } 356 357 if (m == null) { 358 throw new IOException("Unexpected HTTP response: code=" + code + " != " 359 + op.getExpectedHttpResponseCode() + ", " + op.toQueryString() 360 + ", message=" + conn.getResponseMessage()); 361 } else if (m.get(RemoteException.class.getSimpleName()) == null) { 362 return m; 363 } 364 365 IOException re = JsonUtil.toRemoteException(m); 366 // extract UGI-related exceptions and unwrap InvalidToken 367 // the NN mangles these exceptions but the DN does not and may need 368 // to re-fetch a token if either report the token is expired 369 if (re.getMessage() != null && re.getMessage().startsWith( 370 SecurityUtil.FAILED_TO_GET_UGI_MSG_HEADER)) { 371 String[] parts = re.getMessage().split(":\\s+", 3); 372 re = new RemoteException(parts[1], parts[2]); 373 re = ((RemoteException)re).unwrapRemoteException(InvalidToken.class); 374 } 375 throw unwrapException? toIOException(re): re; 376 } 377 return null; 378 } 379 380 /** 381 * Covert an exception to an IOException. 382 * 383 * For a non-IOException, wrap it with IOException. 384 * For a RemoteException, unwrap it. 385 * For an IOException which is not a RemoteException, return it. 386 */ 387 private static IOException toIOException(Exception e) { 388 if (!(e instanceof IOException)) { 389 return new IOException(e); 390 } 391 392 final IOException ioe = (IOException)e; 393 if (!(ioe instanceof RemoteException)) { 394 return ioe; 395 } 396 397 return ((RemoteException)ioe).unwrapRemoteException(); 398 } 399 400 private synchronized InetSocketAddress getCurrentNNAddr() { 401 return nnAddrs[currentNNAddrIndex]; 402 } 403 404 /** 405 * Reset the appropriate state to gracefully fail over to another name node 406 */ 407 private synchronized void resetStateToFailOver() { 408 currentNNAddrIndex = (currentNNAddrIndex + 1) % nnAddrs.length; 409 } 410 411 /** 412 * Return a URL pointing to given path on the namenode. 413 * 414 * @param path to obtain the URL for 415 * @param query string to append to the path 416 * @return namenode URL referring to the given path 417 * @throws IOException on error constructing the URL 418 */ 419 private URL getNamenodeURL(String path, String query) throws IOException { 420 InetSocketAddress nnAddr = getCurrentNNAddr(); 421 final URL url = new URL(getTransportScheme(), nnAddr.getHostName(), 422 nnAddr.getPort(), path + '?' + query); 423 if (LOG.isTraceEnabled()) { 424 LOG.trace("url=" + url); 425 } 426 return url; 427 } 428 429 Param<?,?>[] getAuthParameters(final HttpOpParam.Op op) throws IOException { 430 List<Param<?,?>> authParams = Lists.newArrayList(); 431 // Skip adding delegation token for token operations because these 432 // operations require authentication. 433 Token<?> token = null; 434 if (!op.getRequireAuth()) { 435 token = getDelegationToken(); 436 } 437 if (token != null) { 438 authParams.add(new DelegationParam(token.encodeToUrlString())); 439 } else { 440 UserGroupInformation userUgi = ugi; 441 UserGroupInformation realUgi = userUgi.getRealUser(); 442 if (realUgi != null) { // proxy user 443 authParams.add(new DoAsParam(userUgi.getShortUserName())); 444 userUgi = realUgi; 445 } 446 authParams.add(new UserParam(userUgi.getShortUserName())); 447 } 448 return authParams.toArray(new Param<?,?>[0]); 449 } 450 451 URL toUrl(final HttpOpParam.Op op, final Path fspath, 452 final Param<?,?>... parameters) throws IOException { 453 //initialize URI path and query 454 final String path = PATH_PREFIX 455 + (fspath == null? "/": makeQualified(fspath).toUri().getRawPath()); 456 final String query = op.toQueryString() 457 + Param.toSortedString("&", getAuthParameters(op)) 458 + Param.toSortedString("&", parameters); 459 final URL url = getNamenodeURL(path, query); 460 if (LOG.isTraceEnabled()) { 461 LOG.trace("url=" + url); 462 } 463 return url; 464 } 465 466 /** 467 * This class is for initialing a HTTP connection, connecting to server, 468 * obtaining a response, and also handling retry on failures. 469 */ 470 abstract class AbstractRunner<T> { 471 abstract protected URL getUrl() throws IOException; 472 473 protected final HttpOpParam.Op op; 474 private final boolean redirected; 475 protected ExcludeDatanodesParam excludeDatanodes = new ExcludeDatanodesParam(""); 476 477 private boolean checkRetry; 478 479 protected AbstractRunner(final HttpOpParam.Op op, boolean redirected) { 480 this.op = op; 481 this.redirected = redirected; 482 } 483 484 T run() throws IOException { 485 UserGroupInformation connectUgi = ugi.getRealUser(); 486 if (connectUgi == null) { 487 connectUgi = ugi; 488 } 489 if (op.getRequireAuth()) { 490 connectUgi.checkTGTAndReloginFromKeytab(); 491 } 492 try { 493 // the entire lifecycle of the connection must be run inside the 494 // doAs to ensure authentication is performed correctly 495 return connectUgi.doAs( 496 new PrivilegedExceptionAction<T>() { 497 @Override 498 public T run() throws IOException { 499 return runWithRetry(); 500 } 501 }); 502 } catch (InterruptedException e) { 503 throw new IOException(e); 504 } 505 } 506 507 /** 508 * Two-step requests redirected to a DN 509 * 510 * Create/Append: 511 * Step 1) Submit a Http request with neither auto-redirect nor data. 512 * Step 2) Submit another Http request with the URL from the Location header with data. 513 * 514 * The reason of having two-step create/append is for preventing clients to 515 * send out the data before the redirect. This issue is addressed by the 516 * "Expect: 100-continue" header in HTTP/1.1; see RFC 2616, Section 8.2.3. 517 * Unfortunately, there are software library bugs (e.g. Jetty 6 http server 518 * and Java 6 http client), which do not correctly implement "Expect: 519 * 100-continue". The two-step create/append is a temporary workaround for 520 * the software library bugs. 521 * 522 * Open/Checksum 523 * Also implements two-step connects for other operations redirected to 524 * a DN such as open and checksum 525 */ 526 private HttpURLConnection connect(URL url) throws IOException { 527 //redirect hostname and port 528 String redirectHost = null; 529 530 531 // resolve redirects for a DN operation unless already resolved 532 if (op.getRedirect() && !redirected) { 533 final HttpOpParam.Op redirectOp = 534 HttpOpParam.TemporaryRedirectOp.valueOf(op); 535 final HttpURLConnection conn = connect(redirectOp, url); 536 // application level proxy like httpfs might not issue a redirect 537 if (conn.getResponseCode() == op.getExpectedHttpResponseCode()) { 538 return conn; 539 } 540 try { 541 validateResponse(redirectOp, conn, false); 542 url = new URL(conn.getHeaderField("Location")); 543 redirectHost = url.getHost() + ":" + url.getPort(); 544 } finally { 545 conn.disconnect(); 546 } 547 } 548 try { 549 return connect(op, url); 550 } catch (IOException ioe) { 551 if (redirectHost != null) { 552 if (excludeDatanodes.getValue() != null) { 553 excludeDatanodes = new ExcludeDatanodesParam(redirectHost + "," 554 + excludeDatanodes.getValue()); 555 } else { 556 excludeDatanodes = new ExcludeDatanodesParam(redirectHost); 557 } 558 } 559 throw ioe; 560 } 561 } 562 563 private HttpURLConnection connect(final HttpOpParam.Op op, final URL url) 564 throws IOException { 565 final HttpURLConnection conn = 566 (HttpURLConnection)connectionFactory.openConnection(url); 567 final boolean doOutput = op.getDoOutput(); 568 conn.setRequestMethod(op.getType().toString()); 569 conn.setInstanceFollowRedirects(false); 570 switch (op.getType()) { 571 // if not sending a message body for a POST or PUT operation, need 572 // to ensure the server/proxy knows this 573 case POST: 574 case PUT: { 575 conn.setDoOutput(true); 576 if (!doOutput) { 577 // explicitly setting content-length to 0 won't do spnego!! 578 // opening and closing the stream will send "Content-Length: 0" 579 conn.getOutputStream().close(); 580 } else { 581 conn.setRequestProperty("Content-Type", 582 MediaType.APPLICATION_OCTET_STREAM); 583 conn.setChunkedStreamingMode(32 << 10); //32kB-chunk 584 } 585 break; 586 } 587 default: { 588 conn.setDoOutput(doOutput); 589 break; 590 } 591 } 592 conn.connect(); 593 return conn; 594 } 595 596 private T runWithRetry() throws IOException { 597 /** 598 * Do the real work. 599 * 600 * There are three cases that the code inside the loop can throw an 601 * IOException: 602 * 603 * <ul> 604 * <li>The connection has failed (e.g., ConnectException, 605 * @see FailoverOnNetworkExceptionRetry for more details)</li> 606 * <li>The namenode enters the standby state (i.e., StandbyException).</li> 607 * <li>The server returns errors for the command (i.e., RemoteException)</li> 608 * </ul> 609 * 610 * The call to shouldRetry() will conduct the retry policy. The policy 611 * examines the exception and swallows it if it decides to rerun the work. 612 */ 613 for(int retry = 0; ; retry++) { 614 checkRetry = !redirected; 615 final URL url = getUrl(); 616 try { 617 final HttpURLConnection conn = connect(url); 618 // output streams will validate on close 619 if (!op.getDoOutput()) { 620 validateResponse(op, conn, false); 621 } 622 return getResponse(conn); 623 } catch (AccessControlException ace) { 624 // no retries for auth failures 625 throw ace; 626 } catch (InvalidToken it) { 627 // try to replace the expired token with a new one. the attempt 628 // to acquire a new token must be outside this operation's retry 629 // so if it fails after its own retries, this operation fails too. 630 if (op.getRequireAuth() || !replaceExpiredDelegationToken()) { 631 throw it; 632 } 633 } catch (IOException ioe) { 634 shouldRetry(ioe, retry); 635 } 636 } 637 } 638 639 private void shouldRetry(final IOException ioe, final int retry 640 ) throws IOException { 641 InetSocketAddress nnAddr = getCurrentNNAddr(); 642 if (checkRetry) { 643 try { 644 final RetryPolicy.RetryAction a = retryPolicy.shouldRetry( 645 ioe, retry, 0, true); 646 647 boolean isRetry = a.action == RetryPolicy.RetryAction.RetryDecision.RETRY; 648 boolean isFailoverAndRetry = 649 a.action == RetryPolicy.RetryAction.RetryDecision.FAILOVER_AND_RETRY; 650 651 if (isRetry || isFailoverAndRetry) { 652 LOG.info("Retrying connect to namenode: " + nnAddr 653 + ". Already tried " + retry + " time(s); retry policy is " 654 + retryPolicy + ", delay " + a.delayMillis + "ms."); 655 656 if (isFailoverAndRetry) { 657 resetStateToFailOver(); 658 } 659 660 Thread.sleep(a.delayMillis); 661 return; 662 } 663 } catch(Exception e) { 664 LOG.warn("Original exception is ", ioe); 665 throw toIOException(e); 666 } 667 } 668 throw toIOException(ioe); 669 } 670 671 abstract T getResponse(HttpURLConnection conn) throws IOException; 672 } 673 674 /** 675 * Abstract base class to handle path-based operations with params 676 */ 677 abstract class AbstractFsPathRunner<T> extends AbstractRunner<T> { 678 private final Path fspath; 679 private final Param<?,?>[] parameters; 680 681 AbstractFsPathRunner(final HttpOpParam.Op op, final Path fspath, 682 Param<?,?>... parameters) { 683 super(op, false); 684 this.fspath = fspath; 685 this.parameters = parameters; 686 } 687 688 AbstractFsPathRunner(final HttpOpParam.Op op, Param<?,?>[] parameters, 689 final Path fspath) { 690 super(op, false); 691 this.fspath = fspath; 692 this.parameters = parameters; 693 } 694 695 @Override 696 protected URL getUrl() throws IOException { 697 if (excludeDatanodes.getValue() != null) { 698 Param<?, ?>[] tmpParam = new Param<?, ?>[parameters.length + 1]; 699 System.arraycopy(parameters, 0, tmpParam, 0, parameters.length); 700 tmpParam[parameters.length] = excludeDatanodes; 701 return toUrl(op, fspath, tmpParam); 702 } else { 703 return toUrl(op, fspath, parameters); 704 } 705 } 706 } 707 708 /** 709 * Default path-based implementation expects no json response 710 */ 711 class FsPathRunner extends AbstractFsPathRunner<Void> { 712 FsPathRunner(Op op, Path fspath, Param<?,?>... parameters) { 713 super(op, fspath, parameters); 714 } 715 716 @Override 717 Void getResponse(HttpURLConnection conn) throws IOException { 718 return null; 719 } 720 } 721 722 /** 723 * Handle path-based operations with a json response 724 */ 725 abstract class FsPathResponseRunner<T> extends AbstractFsPathRunner<T> { 726 FsPathResponseRunner(final HttpOpParam.Op op, final Path fspath, 727 Param<?,?>... parameters) { 728 super(op, fspath, parameters); 729 } 730 731 FsPathResponseRunner(final HttpOpParam.Op op, Param<?,?>[] parameters, 732 final Path fspath) { 733 super(op, parameters, fspath); 734 } 735 736 @Override 737 final T getResponse(HttpURLConnection conn) throws IOException { 738 try { 739 final Map<?,?> json = jsonParse(conn, false); 740 if (json == null) { 741 // match exception class thrown by parser 742 throw new IllegalStateException("Missing response"); 743 } 744 return decodeResponse(json); 745 } catch (IOException ioe) { 746 throw ioe; 747 } catch (Exception e) { // catch json parser errors 748 final IOException ioe = 749 new IOException("Response decoding failure: "+e.toString(), e); 750 if (LOG.isDebugEnabled()) { 751 LOG.debug(ioe); 752 } 753 throw ioe; 754 } finally { 755 conn.disconnect(); 756 } 757 } 758 759 abstract T decodeResponse(Map<?,?> json) throws IOException; 760 } 761 762 /** 763 * Handle path-based operations with json boolean response 764 */ 765 class FsPathBooleanRunner extends FsPathResponseRunner<Boolean> { 766 FsPathBooleanRunner(Op op, Path fspath, Param<?,?>... parameters) { 767 super(op, fspath, parameters); 768 } 769 770 @Override 771 Boolean decodeResponse(Map<?,?> json) throws IOException { 772 return (Boolean)json.get("boolean"); 773 } 774 } 775 776 /** 777 * Handle create/append output streams 778 */ 779 class FsPathOutputStreamRunner extends AbstractFsPathRunner<FSDataOutputStream> { 780 private final int bufferSize; 781 782 FsPathOutputStreamRunner(Op op, Path fspath, int bufferSize, 783 Param<?,?>... parameters) { 784 super(op, fspath, parameters); 785 this.bufferSize = bufferSize; 786 } 787 788 @Override 789 FSDataOutputStream getResponse(final HttpURLConnection conn) 790 throws IOException { 791 return new FSDataOutputStream(new BufferedOutputStream( 792 conn.getOutputStream(), bufferSize), statistics) { 793 @Override 794 public void close() throws IOException { 795 try { 796 super.close(); 797 } finally { 798 try { 799 validateResponse(op, conn, true); 800 } finally { 801 conn.disconnect(); 802 } 803 } 804 } 805 }; 806 } 807 } 808 809 class FsPathConnectionRunner extends AbstractFsPathRunner<HttpURLConnection> { 810 FsPathConnectionRunner(Op op, Path fspath, Param<?,?>... parameters) { 811 super(op, fspath, parameters); 812 } 813 @Override 814 HttpURLConnection getResponse(final HttpURLConnection conn) 815 throws IOException { 816 return conn; 817 } 818 } 819 820 /** 821 * Used by open() which tracks the resolved url itself 822 */ 823 final class URLRunner extends AbstractRunner<HttpURLConnection> { 824 private final URL url; 825 @Override 826 protected URL getUrl() { 827 return url; 828 } 829 830 protected URLRunner(final HttpOpParam.Op op, final URL url, boolean redirected) { 831 super(op, redirected); 832 this.url = url; 833 } 834 835 @Override 836 HttpURLConnection getResponse(HttpURLConnection conn) throws IOException { 837 return conn; 838 } 839 } 840 841 private FsPermission applyUMask(FsPermission permission) { 842 if (permission == null) { 843 permission = FsPermission.getDefault(); 844 } 845 return permission.applyUMask(FsPermission.getUMask(getConf())); 846 } 847 848 private HdfsFileStatus getHdfsFileStatus(Path f) throws IOException { 849 final HttpOpParam.Op op = GetOpParam.Op.GETFILESTATUS; 850 HdfsFileStatus status = new FsPathResponseRunner<HdfsFileStatus>(op, f) { 851 @Override 852 HdfsFileStatus decodeResponse(Map<?,?> json) { 853 return JsonUtil.toFileStatus(json, true); 854 } 855 }.run(); 856 if (status == null) { 857 throw new FileNotFoundException("File does not exist: " + f); 858 } 859 return status; 860 } 861 862 @Override 863 public FileStatus getFileStatus(Path f) throws IOException { 864 statistics.incrementReadOps(1); 865 return makeQualified(getHdfsFileStatus(f), f); 866 } 867 868 private FileStatus makeQualified(HdfsFileStatus f, Path parent) { 869 return new FileStatus(f.getLen(), f.isDir(), f.getReplication(), 870 f.getBlockSize(), f.getModificationTime(), f.getAccessTime(), 871 f.getPermission(), f.getOwner(), f.getGroup(), 872 f.isSymlink() ? new Path(f.getSymlink()) : null, 873 f.getFullPath(parent).makeQualified(getUri(), getWorkingDirectory())); 874 } 875 876 @Override 877 public AclStatus getAclStatus(Path f) throws IOException { 878 final HttpOpParam.Op op = GetOpParam.Op.GETACLSTATUS; 879 AclStatus status = new FsPathResponseRunner<AclStatus>(op, f) { 880 @Override 881 AclStatus decodeResponse(Map<?,?> json) { 882 return JsonUtil.toAclStatus(json); 883 } 884 }.run(); 885 if (status == null) { 886 throw new FileNotFoundException("File does not exist: " + f); 887 } 888 return status; 889 } 890 891 @Override 892 public boolean mkdirs(Path f, FsPermission permission) throws IOException { 893 statistics.incrementWriteOps(1); 894 final HttpOpParam.Op op = PutOpParam.Op.MKDIRS; 895 return new FsPathBooleanRunner(op, f, 896 new PermissionParam(applyUMask(permission)) 897 ).run(); 898 } 899 900 /** 901 * Create a symlink pointing to the destination path. 902 * @see org.apache.hadoop.fs.Hdfs#createSymlink(Path, Path, boolean) 903 */ 904 public void createSymlink(Path destination, Path f, boolean createParent 905 ) throws IOException { 906 statistics.incrementWriteOps(1); 907 final HttpOpParam.Op op = PutOpParam.Op.CREATESYMLINK; 908 new FsPathRunner(op, f, 909 new DestinationParam(makeQualified(destination).toUri().getPath()), 910 new CreateParentParam(createParent) 911 ).run(); 912 } 913 914 @Override 915 public boolean rename(final Path src, final Path dst) throws IOException { 916 statistics.incrementWriteOps(1); 917 final HttpOpParam.Op op = PutOpParam.Op.RENAME; 918 return new FsPathBooleanRunner(op, src, 919 new DestinationParam(makeQualified(dst).toUri().getPath()) 920 ).run(); 921 } 922 923 @SuppressWarnings("deprecation") 924 @Override 925 public void rename(final Path src, final Path dst, 926 final Options.Rename... options) throws IOException { 927 statistics.incrementWriteOps(1); 928 final HttpOpParam.Op op = PutOpParam.Op.RENAME; 929 new FsPathRunner(op, src, 930 new DestinationParam(makeQualified(dst).toUri().getPath()), 931 new RenameOptionSetParam(options) 932 ).run(); 933 } 934 935 @Override 936 public void setXAttr(Path p, String name, byte[] value, 937 EnumSet<XAttrSetFlag> flag) throws IOException { 938 statistics.incrementWriteOps(1); 939 final HttpOpParam.Op op = PutOpParam.Op.SETXATTR; 940 if (value != null) { 941 new FsPathRunner(op, p, new XAttrNameParam(name), new XAttrValueParam( 942 XAttrCodec.encodeValue(value, XAttrCodec.HEX)), 943 new XAttrSetFlagParam(flag)).run(); 944 } else { 945 new FsPathRunner(op, p, new XAttrNameParam(name), 946 new XAttrSetFlagParam(flag)).run(); 947 } 948 } 949 950 @Override 951 public byte[] getXAttr(Path p, final String name) throws IOException { 952 final HttpOpParam.Op op = GetOpParam.Op.GETXATTRS; 953 return new FsPathResponseRunner<byte[]>(op, p, new XAttrNameParam(name), 954 new XAttrEncodingParam(XAttrCodec.HEX)) { 955 @Override 956 byte[] decodeResponse(Map<?, ?> json) throws IOException { 957 return JsonUtil.getXAttr(json, name); 958 } 959 }.run(); 960 } 961 962 @Override 963 public Map<String, byte[]> getXAttrs(Path p) throws IOException { 964 final HttpOpParam.Op op = GetOpParam.Op.GETXATTRS; 965 return new FsPathResponseRunner<Map<String, byte[]>>(op, p, 966 new XAttrEncodingParam(XAttrCodec.HEX)) { 967 @Override 968 Map<String, byte[]> decodeResponse(Map<?, ?> json) throws IOException { 969 return JsonUtil.toXAttrs(json); 970 } 971 }.run(); 972 } 973 974 @Override 975 public Map<String, byte[]> getXAttrs(Path p, final List<String> names) 976 throws IOException { 977 Preconditions.checkArgument(names != null && !names.isEmpty(), 978 "XAttr names cannot be null or empty."); 979 Param<?,?>[] parameters = new Param<?,?>[names.size() + 1]; 980 for (int i = 0; i < parameters.length - 1; i++) { 981 parameters[i] = new XAttrNameParam(names.get(i)); 982 } 983 parameters[parameters.length - 1] = new XAttrEncodingParam(XAttrCodec.HEX); 984 985 final HttpOpParam.Op op = GetOpParam.Op.GETXATTRS; 986 return new FsPathResponseRunner<Map<String, byte[]>>(op, parameters, p) { 987 @Override 988 Map<String, byte[]> decodeResponse(Map<?, ?> json) throws IOException { 989 return JsonUtil.toXAttrs(json); 990 } 991 }.run(); 992 } 993 994 @Override 995 public List<String> listXAttrs(Path p) throws IOException { 996 final HttpOpParam.Op op = GetOpParam.Op.LISTXATTRS; 997 return new FsPathResponseRunner<List<String>>(op, p) { 998 @Override 999 List<String> decodeResponse(Map<?, ?> json) throws IOException { 1000 return JsonUtil.toXAttrNames(json); 1001 } 1002 }.run(); 1003 } 1004 1005 @Override 1006 public void removeXAttr(Path p, String name) throws IOException { 1007 statistics.incrementWriteOps(1); 1008 final HttpOpParam.Op op = PutOpParam.Op.REMOVEXATTR; 1009 new FsPathRunner(op, p, new XAttrNameParam(name)).run(); 1010 } 1011 1012 @Override 1013 public void setOwner(final Path p, final String owner, final String group 1014 ) throws IOException { 1015 if (owner == null && group == null) { 1016 throw new IOException("owner == null && group == null"); 1017 } 1018 1019 statistics.incrementWriteOps(1); 1020 final HttpOpParam.Op op = PutOpParam.Op.SETOWNER; 1021 new FsPathRunner(op, p, 1022 new OwnerParam(owner), new GroupParam(group) 1023 ).run(); 1024 } 1025 1026 @Override 1027 public void setPermission(final Path p, final FsPermission permission 1028 ) throws IOException { 1029 statistics.incrementWriteOps(1); 1030 final HttpOpParam.Op op = PutOpParam.Op.SETPERMISSION; 1031 new FsPathRunner(op, p,new PermissionParam(permission)).run(); 1032 } 1033 1034 @Override 1035 public void modifyAclEntries(Path path, List<AclEntry> aclSpec) 1036 throws IOException { 1037 statistics.incrementWriteOps(1); 1038 final HttpOpParam.Op op = PutOpParam.Op.MODIFYACLENTRIES; 1039 new FsPathRunner(op, path, new AclPermissionParam(aclSpec)).run(); 1040 } 1041 1042 @Override 1043 public void removeAclEntries(Path path, List<AclEntry> aclSpec) 1044 throws IOException { 1045 statistics.incrementWriteOps(1); 1046 final HttpOpParam.Op op = PutOpParam.Op.REMOVEACLENTRIES; 1047 new FsPathRunner(op, path, new AclPermissionParam(aclSpec)).run(); 1048 } 1049 1050 @Override 1051 public void removeDefaultAcl(Path path) throws IOException { 1052 statistics.incrementWriteOps(1); 1053 final HttpOpParam.Op op = PutOpParam.Op.REMOVEDEFAULTACL; 1054 new FsPathRunner(op, path).run(); 1055 } 1056 1057 @Override 1058 public void removeAcl(Path path) throws IOException { 1059 statistics.incrementWriteOps(1); 1060 final HttpOpParam.Op op = PutOpParam.Op.REMOVEACL; 1061 new FsPathRunner(op, path).run(); 1062 } 1063 1064 @Override 1065 public void setAcl(final Path p, final List<AclEntry> aclSpec) 1066 throws IOException { 1067 statistics.incrementWriteOps(1); 1068 final HttpOpParam.Op op = PutOpParam.Op.SETACL; 1069 new FsPathRunner(op, p, new AclPermissionParam(aclSpec)).run(); 1070 } 1071 1072 @Override 1073 public Path createSnapshot(final Path path, final String snapshotName) 1074 throws IOException { 1075 statistics.incrementWriteOps(1); 1076 final HttpOpParam.Op op = PutOpParam.Op.CREATESNAPSHOT; 1077 Path spath = new FsPathResponseRunner<Path>(op, path, 1078 new SnapshotNameParam(snapshotName)) { 1079 @Override 1080 Path decodeResponse(Map<?,?> json) { 1081 return new Path((String) json.get(Path.class.getSimpleName())); 1082 } 1083 }.run(); 1084 return spath; 1085 } 1086 1087 @Override 1088 public void deleteSnapshot(final Path path, final String snapshotName) 1089 throws IOException { 1090 statistics.incrementWriteOps(1); 1091 final HttpOpParam.Op op = DeleteOpParam.Op.DELETESNAPSHOT; 1092 new FsPathRunner(op, path, new SnapshotNameParam(snapshotName)).run(); 1093 } 1094 1095 @Override 1096 public void renameSnapshot(final Path path, final String snapshotOldName, 1097 final String snapshotNewName) throws IOException { 1098 statistics.incrementWriteOps(1); 1099 final HttpOpParam.Op op = PutOpParam.Op.RENAMESNAPSHOT; 1100 new FsPathRunner(op, path, new OldSnapshotNameParam(snapshotOldName), 1101 new SnapshotNameParam(snapshotNewName)).run(); 1102 } 1103 1104 @Override 1105 public boolean setReplication(final Path p, final short replication 1106 ) throws IOException { 1107 statistics.incrementWriteOps(1); 1108 final HttpOpParam.Op op = PutOpParam.Op.SETREPLICATION; 1109 return new FsPathBooleanRunner(op, p, 1110 new ReplicationParam(replication) 1111 ).run(); 1112 } 1113 1114 @Override 1115 public void setTimes(final Path p, final long mtime, final long atime 1116 ) throws IOException { 1117 statistics.incrementWriteOps(1); 1118 final HttpOpParam.Op op = PutOpParam.Op.SETTIMES; 1119 new FsPathRunner(op, p, 1120 new ModificationTimeParam(mtime), 1121 new AccessTimeParam(atime) 1122 ).run(); 1123 } 1124 1125 @Override 1126 public long getDefaultBlockSize() { 1127 return getConf().getLongBytes(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, 1128 DFSConfigKeys.DFS_BLOCK_SIZE_DEFAULT); 1129 } 1130 1131 @Override 1132 public short getDefaultReplication() { 1133 return (short)getConf().getInt(DFSConfigKeys.DFS_REPLICATION_KEY, 1134 DFSConfigKeys.DFS_REPLICATION_DEFAULT); 1135 } 1136 1137 @Override 1138 public void concat(final Path trg, final Path [] srcs) throws IOException { 1139 statistics.incrementWriteOps(1); 1140 final HttpOpParam.Op op = PostOpParam.Op.CONCAT; 1141 new FsPathRunner(op, trg, new ConcatSourcesParam(srcs)).run(); 1142 } 1143 1144 @Override 1145 public FSDataOutputStream create(final Path f, final FsPermission permission, 1146 final boolean overwrite, final int bufferSize, final short replication, 1147 final long blockSize, final Progressable progress) throws IOException { 1148 statistics.incrementWriteOps(1); 1149 1150 final HttpOpParam.Op op = PutOpParam.Op.CREATE; 1151 return new FsPathOutputStreamRunner(op, f, bufferSize, 1152 new PermissionParam(applyUMask(permission)), 1153 new OverwriteParam(overwrite), 1154 new BufferSizeParam(bufferSize), 1155 new ReplicationParam(replication), 1156 new BlockSizeParam(blockSize) 1157 ).run(); 1158 } 1159 1160 @Override 1161 public FSDataOutputStream append(final Path f, final int bufferSize, 1162 final Progressable progress) throws IOException { 1163 statistics.incrementWriteOps(1); 1164 1165 final HttpOpParam.Op op = PostOpParam.Op.APPEND; 1166 return new FsPathOutputStreamRunner(op, f, bufferSize, 1167 new BufferSizeParam(bufferSize) 1168 ).run(); 1169 } 1170 1171 @Override 1172 public boolean truncate(Path f, long newLength) throws IOException { 1173 statistics.incrementWriteOps(1); 1174 1175 final HttpOpParam.Op op = PostOpParam.Op.TRUNCATE; 1176 return new FsPathBooleanRunner(op, f, new NewLengthParam(newLength)).run(); 1177 } 1178 1179 @Override 1180 public boolean delete(Path f, boolean recursive) throws IOException { 1181 final HttpOpParam.Op op = DeleteOpParam.Op.DELETE; 1182 return new FsPathBooleanRunner(op, f, 1183 new RecursiveParam(recursive) 1184 ).run(); 1185 } 1186 1187 @Override 1188 public FSDataInputStream open(final Path f, final int buffersize 1189 ) throws IOException { 1190 statistics.incrementReadOps(1); 1191 final HttpOpParam.Op op = GetOpParam.Op.OPEN; 1192 // use a runner so the open can recover from an invalid token 1193 FsPathConnectionRunner runner = 1194 new FsPathConnectionRunner(op, f, new BufferSizeParam(buffersize)); 1195 return new FSDataInputStream(new OffsetUrlInputStream( 1196 new UnresolvedUrlOpener(runner), new OffsetUrlOpener(null))); 1197 } 1198 1199 @Override 1200 public synchronized void close() throws IOException { 1201 try { 1202 if (canRefreshDelegationToken && delegationToken != null) { 1203 cancelDelegationToken(delegationToken); 1204 } 1205 } catch (IOException ioe) { 1206 LOG.debug("Token cancel failed: "+ioe); 1207 } finally { 1208 super.close(); 1209 } 1210 } 1211 1212 // use FsPathConnectionRunner to ensure retries for InvalidTokens 1213 class UnresolvedUrlOpener extends ByteRangeInputStream.URLOpener { 1214 private final FsPathConnectionRunner runner; 1215 UnresolvedUrlOpener(FsPathConnectionRunner runner) { 1216 super(null); 1217 this.runner = runner; 1218 } 1219 1220 @Override 1221 protected HttpURLConnection connect(long offset, boolean resolved) 1222 throws IOException { 1223 assert offset == 0; 1224 HttpURLConnection conn = runner.run(); 1225 setURL(conn.getURL()); 1226 return conn; 1227 } 1228 } 1229 1230 class OffsetUrlOpener extends ByteRangeInputStream.URLOpener { 1231 OffsetUrlOpener(final URL url) { 1232 super(url); 1233 } 1234 1235 /** Setup offset url and connect. */ 1236 @Override 1237 protected HttpURLConnection connect(final long offset, 1238 final boolean resolved) throws IOException { 1239 final URL offsetUrl = offset == 0L? url 1240 : new URL(url + "&" + new OffsetParam(offset)); 1241 return new URLRunner(GetOpParam.Op.OPEN, offsetUrl, resolved).run(); 1242 } 1243 } 1244 1245 private static final String OFFSET_PARAM_PREFIX = OffsetParam.NAME + "="; 1246 1247 /** Remove offset parameter, if there is any, from the url */ 1248 static URL removeOffsetParam(final URL url) throws MalformedURLException { 1249 String query = url.getQuery(); 1250 if (query == null) { 1251 return url; 1252 } 1253 final String lower = StringUtils.toLowerCase(query); 1254 if (!lower.startsWith(OFFSET_PARAM_PREFIX) 1255 && !lower.contains("&" + OFFSET_PARAM_PREFIX)) { 1256 return url; 1257 } 1258 1259 //rebuild query 1260 StringBuilder b = null; 1261 for(final StringTokenizer st = new StringTokenizer(query, "&"); 1262 st.hasMoreTokens();) { 1263 final String token = st.nextToken(); 1264 if (!StringUtils.toLowerCase(token).startsWith(OFFSET_PARAM_PREFIX)) { 1265 if (b == null) { 1266 b = new StringBuilder("?").append(token); 1267 } else { 1268 b.append('&').append(token); 1269 } 1270 } 1271 } 1272 query = b == null? "": b.toString(); 1273 1274 final String urlStr = url.toString(); 1275 return new URL(urlStr.substring(0, urlStr.indexOf('?')) + query); 1276 } 1277 1278 static class OffsetUrlInputStream extends ByteRangeInputStream { 1279 OffsetUrlInputStream(UnresolvedUrlOpener o, OffsetUrlOpener r) 1280 throws IOException { 1281 super(o, r); 1282 } 1283 1284 /** Remove offset parameter before returning the resolved url. */ 1285 @Override 1286 protected URL getResolvedUrl(final HttpURLConnection connection 1287 ) throws MalformedURLException { 1288 return removeOffsetParam(connection.getURL()); 1289 } 1290 } 1291 1292 @Override 1293 public FileStatus[] listStatus(final Path f) throws IOException { 1294 statistics.incrementReadOps(1); 1295 1296 final HttpOpParam.Op op = GetOpParam.Op.LISTSTATUS; 1297 return new FsPathResponseRunner<FileStatus[]>(op, f) { 1298 @Override 1299 FileStatus[] decodeResponse(Map<?,?> json) { 1300 final Map<?, ?> rootmap = (Map<?, ?>)json.get(FileStatus.class.getSimpleName() + "es"); 1301 final List<?> array = JsonUtil.getList( 1302 rootmap, FileStatus.class.getSimpleName()); 1303 1304 //convert FileStatus 1305 final FileStatus[] statuses = new FileStatus[array.size()]; 1306 int i = 0; 1307 for (Object object : array) { 1308 final Map<?, ?> m = (Map<?, ?>) object; 1309 statuses[i++] = makeQualified(JsonUtil.toFileStatus(m, false), f); 1310 } 1311 return statuses; 1312 } 1313 }.run(); 1314 } 1315 1316 @Override 1317 public Token<DelegationTokenIdentifier> getDelegationToken( 1318 final String renewer) throws IOException { 1319 try { 1320 final HttpOpParam.Op op = GetOpParam.Op.GETDELEGATIONTOKEN; 1321 Token<DelegationTokenIdentifier> token = 1322 new FsPathResponseRunner<Token<DelegationTokenIdentifier>>( 1323 op, null, new RenewerParam(renewer)) { 1324 @Override 1325 Token<DelegationTokenIdentifier> decodeResponse(Map<?,?> json) 1326 throws IOException { 1327 return JsonUtil.toDelegationToken(json); 1328 } 1329 }.run(); 1330 if (token != null) { 1331 token.setService(tokenServiceName); 1332 return token; 1333 } else { 1334 if (disallowFallbackToInsecureCluster) { 1335 throw new AccessControlException(CANT_FALLBACK_TO_INSECURE_MSG); 1336 } 1337 } 1338 } catch (Exception e) { 1339 LOG.warn(e.getMessage()); 1340 LOG.debug(e.getMessage(), e); 1341 } 1342 return null; 1343 } 1344 1345 @Override 1346 public synchronized Token<?> getRenewToken() { 1347 return delegationToken; 1348 } 1349 1350 @Override 1351 public <T extends TokenIdentifier> void setDelegationToken( 1352 final Token<T> token) { 1353 synchronized (this) { 1354 delegationToken = token; 1355 } 1356 } 1357 1358 @Override 1359 public synchronized long renewDelegationToken(final Token<?> token 1360 ) throws IOException { 1361 final HttpOpParam.Op op = PutOpParam.Op.RENEWDELEGATIONTOKEN; 1362 return new FsPathResponseRunner<Long>(op, null, 1363 new TokenArgumentParam(token.encodeToUrlString())) { 1364 @Override 1365 Long decodeResponse(Map<?,?> json) throws IOException { 1366 return ((Number) json.get("long")).longValue(); 1367 } 1368 }.run(); 1369 } 1370 1371 @Override 1372 public synchronized void cancelDelegationToken(final Token<?> token 1373 ) throws IOException { 1374 final HttpOpParam.Op op = PutOpParam.Op.CANCELDELEGATIONTOKEN; 1375 new FsPathRunner(op, null, 1376 new TokenArgumentParam(token.encodeToUrlString()) 1377 ).run(); 1378 } 1379 1380 @Override 1381 public BlockLocation[] getFileBlockLocations(final FileStatus status, 1382 final long offset, final long length) throws IOException { 1383 if (status == null) { 1384 return null; 1385 } 1386 return getFileBlockLocations(status.getPath(), offset, length); 1387 } 1388 1389 @Override 1390 public BlockLocation[] getFileBlockLocations(final Path p, 1391 final long offset, final long length) throws IOException { 1392 statistics.incrementReadOps(1); 1393 1394 final HttpOpParam.Op op = GetOpParam.Op.GET_BLOCK_LOCATIONS; 1395 return new FsPathResponseRunner<BlockLocation[]>(op, p, 1396 new OffsetParam(offset), new LengthParam(length)) { 1397 @Override 1398 BlockLocation[] decodeResponse(Map<?,?> json) throws IOException { 1399 return DFSUtil.locatedBlocks2Locations( 1400 JsonUtil.toLocatedBlocks(json)); 1401 } 1402 }.run(); 1403 } 1404 1405 @Override 1406 public void access(final Path path, final FsAction mode) throws IOException { 1407 final HttpOpParam.Op op = GetOpParam.Op.CHECKACCESS; 1408 new FsPathRunner(op, path, new FsActionParam(mode)).run(); 1409 } 1410 1411 @Override 1412 public ContentSummary getContentSummary(final Path p) throws IOException { 1413 statistics.incrementReadOps(1); 1414 1415 final HttpOpParam.Op op = GetOpParam.Op.GETCONTENTSUMMARY; 1416 return new FsPathResponseRunner<ContentSummary>(op, p) { 1417 @Override 1418 ContentSummary decodeResponse(Map<?,?> json) { 1419 return JsonUtil.toContentSummary(json); 1420 } 1421 }.run(); 1422 } 1423 1424 @Override 1425 public MD5MD5CRC32FileChecksum getFileChecksum(final Path p 1426 ) throws IOException { 1427 statistics.incrementReadOps(1); 1428 1429 final HttpOpParam.Op op = GetOpParam.Op.GETFILECHECKSUM; 1430 return new FsPathResponseRunner<MD5MD5CRC32FileChecksum>(op, p) { 1431 @Override 1432 MD5MD5CRC32FileChecksum decodeResponse(Map<?,?> json) throws IOException { 1433 return JsonUtil.toMD5MD5CRC32FileChecksum(json); 1434 } 1435 }.run(); 1436 } 1437 1438 /** 1439 * Resolve an HDFS URL into real INetSocketAddress. It works like a DNS 1440 * resolver when the URL points to an non-HA cluster. When the URL points to 1441 * an HA cluster with its logical name, the resolver further resolves the 1442 * logical name(i.e., the authority in the URL) into real namenode addresses. 1443 */ 1444 private InetSocketAddress[] resolveNNAddr() throws IOException { 1445 Configuration conf = getConf(); 1446 final String scheme = uri.getScheme(); 1447 1448 ArrayList<InetSocketAddress> ret = new ArrayList<InetSocketAddress>(); 1449 1450 if (!HAUtil.isLogicalUri(conf, uri)) { 1451 InetSocketAddress addr = NetUtils.createSocketAddr(uri.getAuthority(), 1452 getDefaultPort()); 1453 ret.add(addr); 1454 1455 } else { 1456 Map<String, Map<String, InetSocketAddress>> addresses = DFSUtil 1457 .getHaNnWebHdfsAddresses(conf, scheme); 1458 1459 // Extract the entry corresponding to the logical name. 1460 Map<String, InetSocketAddress> addrs = addresses.get(uri.getHost()); 1461 for (InetSocketAddress addr : addrs.values()) { 1462 ret.add(addr); 1463 } 1464 } 1465 1466 InetSocketAddress[] r = new InetSocketAddress[ret.size()]; 1467 return ret.toArray(r); 1468 } 1469 1470 @Override 1471 public String getCanonicalServiceName() { 1472 return tokenServiceName == null ? super.getCanonicalServiceName() 1473 : tokenServiceName.toString(); 1474 } 1475 1476 @VisibleForTesting 1477 InetSocketAddress[] getResolvedNNAddr() { 1478 return nnAddrs; 1479 } 1480}