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