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