001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.hdfs.web; 020 021 import java.io.BufferedOutputStream; 022 import java.io.FileNotFoundException; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.InputStreamReader; 026 import java.net.HttpURLConnection; 027 import java.net.InetSocketAddress; 028 import java.net.MalformedURLException; 029 import java.net.URI; 030 import java.net.URL; 031 import java.security.PrivilegedExceptionAction; 032 import java.util.List; 033 import java.util.Map; 034 import java.util.StringTokenizer; 035 036 import javax.ws.rs.core.MediaType; 037 038 import org.apache.commons.logging.Log; 039 import org.apache.commons.logging.LogFactory; 040 import org.apache.hadoop.conf.Configuration; 041 import org.apache.hadoop.fs.BlockLocation; 042 import org.apache.hadoop.fs.ContentSummary; 043 import org.apache.hadoop.fs.DelegationTokenRenewer; 044 import org.apache.hadoop.fs.FSDataInputStream; 045 import org.apache.hadoop.fs.FSDataOutputStream; 046 import org.apache.hadoop.fs.FileStatus; 047 import org.apache.hadoop.fs.FileSystem; 048 import org.apache.hadoop.fs.MD5MD5CRC32FileChecksum; 049 import org.apache.hadoop.fs.Options; 050 import org.apache.hadoop.fs.Path; 051 import org.apache.hadoop.fs.permission.AclEntry; 052 import org.apache.hadoop.fs.permission.AclStatus; 053 import org.apache.hadoop.fs.permission.FsPermission; 054 import org.apache.hadoop.hdfs.DFSConfigKeys; 055 import org.apache.hadoop.hdfs.DFSUtil; 056 import org.apache.hadoop.hdfs.HAUtil; 057 import org.apache.hadoop.hdfs.protocol.HdfsFileStatus; 058 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; 059 import org.apache.hadoop.hdfs.server.namenode.SafeModeException; 060 import org.apache.hadoop.hdfs.web.resources.AccessTimeParam; 061 import org.apache.hadoop.hdfs.web.resources.AclPermissionParam; 062 import org.apache.hadoop.hdfs.web.resources.BlockSizeParam; 063 import org.apache.hadoop.hdfs.web.resources.BufferSizeParam; 064 import org.apache.hadoop.hdfs.web.resources.ConcatSourcesParam; 065 import org.apache.hadoop.hdfs.web.resources.CreateParentParam; 066 import org.apache.hadoop.hdfs.web.resources.DelegationParam; 067 import org.apache.hadoop.hdfs.web.resources.DeleteOpParam; 068 import org.apache.hadoop.hdfs.web.resources.DestinationParam; 069 import org.apache.hadoop.hdfs.web.resources.DoAsParam; 070 import org.apache.hadoop.hdfs.web.resources.GetOpParam; 071 import org.apache.hadoop.hdfs.web.resources.GroupParam; 072 import org.apache.hadoop.hdfs.web.resources.HttpOpParam; 073 import org.apache.hadoop.hdfs.web.resources.LengthParam; 074 import org.apache.hadoop.hdfs.web.resources.ModificationTimeParam; 075 import org.apache.hadoop.hdfs.web.resources.OffsetParam; 076 import org.apache.hadoop.hdfs.web.resources.OverwriteParam; 077 import org.apache.hadoop.hdfs.web.resources.OwnerParam; 078 import org.apache.hadoop.hdfs.web.resources.Param; 079 import org.apache.hadoop.hdfs.web.resources.PermissionParam; 080 import org.apache.hadoop.hdfs.web.resources.PostOpParam; 081 import org.apache.hadoop.hdfs.web.resources.PutOpParam; 082 import org.apache.hadoop.hdfs.web.resources.RecursiveParam; 083 import org.apache.hadoop.hdfs.web.resources.RenameOptionSetParam; 084 import org.apache.hadoop.hdfs.web.resources.RenewerParam; 085 import org.apache.hadoop.hdfs.web.resources.ReplicationParam; 086 import org.apache.hadoop.hdfs.web.resources.TokenArgumentParam; 087 import org.apache.hadoop.hdfs.web.resources.UserParam; 088 import org.apache.hadoop.io.Text; 089 import org.apache.hadoop.io.retry.RetryPolicies; 090 import org.apache.hadoop.io.retry.RetryPolicy; 091 import org.apache.hadoop.io.retry.RetryUtils; 092 import org.apache.hadoop.ipc.RemoteException; 093 import org.apache.hadoop.net.NetUtils; 094 import org.apache.hadoop.security.SecurityUtil; 095 import org.apache.hadoop.security.UserGroupInformation; 096 import org.apache.hadoop.security.authentication.client.AuthenticationException; 097 import org.apache.hadoop.security.token.SecretManager.InvalidToken; 098 import org.apache.hadoop.security.token.Token; 099 import org.apache.hadoop.security.token.TokenIdentifier; 100 import org.apache.hadoop.util.Progressable; 101 import org.mortbay.util.ajax.JSON; 102 103 import com.google.common.annotations.VisibleForTesting; 104 import com.google.common.base.Charsets; 105 import com.google.common.collect.Lists; 106 107 /** A FileSystem for HDFS over the web. */ 108 public class WebHdfsFileSystem extends FileSystem 109 implements DelegationTokenRenewer.Renewable, TokenAspect.TokenManagementDelegator { 110 public static final Log LOG = LogFactory.getLog(WebHdfsFileSystem.class); 111 /** File System URI: {SCHEME}://namenode:port/path/to/file */ 112 public static final String SCHEME = "webhdfs"; 113 /** WebHdfs version. */ 114 public static final int VERSION = 1; 115 /** Http URI: http://namenode:port/{PATH_PREFIX}/path/to/file */ 116 public static final String PATH_PREFIX = "/" + SCHEME + "/v" + VERSION; 117 118 /** Default connection factory may be overridden in tests to use smaller timeout values */ 119 protected URLConnectionFactory connectionFactory; 120 121 /** Delegation token kind */ 122 public static final Text TOKEN_KIND = new Text("WEBHDFS delegation"); 123 protected TokenAspect<? extends WebHdfsFileSystem> tokenAspect; 124 125 private UserGroupInformation ugi; 126 private URI uri; 127 private Token<?> delegationToken; 128 protected Text tokenServiceName; 129 private RetryPolicy retryPolicy = null; 130 private Path workingDir; 131 private InetSocketAddress nnAddrs[]; 132 private int currentNNAddrIndex; 133 134 /** 135 * Return the protocol scheme for the FileSystem. 136 * <p/> 137 * 138 * @return <code>webhdfs</code> 139 */ 140 @Override 141 public String getScheme() { 142 return SCHEME; 143 } 144 145 /** 146 * return the underlying transport protocol (http / https). 147 */ 148 protected String getTransportScheme() { 149 return "http"; 150 } 151 152 /** 153 * Initialize tokenAspect. This function is intended to 154 * be overridden by SWebHdfsFileSystem. 155 */ 156 protected synchronized void initializeTokenAspect() { 157 tokenAspect = new TokenAspect<WebHdfsFileSystem>(this, tokenServiceName, 158 TOKEN_KIND); 159 } 160 161 @Override 162 public synchronized void initialize(URI uri, Configuration conf 163 ) throws IOException { 164 super.initialize(uri, conf); 165 setConf(conf); 166 /** set user pattern based on configuration file */ 167 UserParam.setUserPattern(conf.get( 168 DFSConfigKeys.DFS_WEBHDFS_USER_PATTERN_KEY, 169 DFSConfigKeys.DFS_WEBHDFS_USER_PATTERN_DEFAULT)); 170 171 connectionFactory = URLConnectionFactory 172 .newDefaultURLConnectionFactory(conf); 173 174 ugi = UserGroupInformation.getCurrentUser(); 175 this.uri = URI.create(uri.getScheme() + "://" + uri.getAuthority()); 176 this.nnAddrs = DFSUtil.resolveWebHdfsUri(this.uri, conf); 177 178 boolean isHA = HAUtil.isLogicalUri(conf, this.uri); 179 // In non-HA case, the code needs to call getCanonicalUri() in order to 180 // handle the case where no port is specified in the URI 181 this.tokenServiceName = isHA ? HAUtil.buildTokenServiceForLogicalUri(uri) 182 : SecurityUtil.buildTokenService(getCanonicalUri()); 183 initializeTokenAspect(); 184 185 if (!isHA) { 186 this.retryPolicy = 187 RetryUtils.getDefaultRetryPolicy( 188 conf, 189 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_ENABLED_KEY, 190 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_ENABLED_DEFAULT, 191 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_SPEC_KEY, 192 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_POLICY_SPEC_DEFAULT, 193 SafeModeException.class); 194 } else { 195 196 int maxFailoverAttempts = conf.getInt( 197 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_MAX_ATTEMPTS_KEY, 198 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_MAX_ATTEMPTS_DEFAULT); 199 int maxRetryAttempts = conf.getInt( 200 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_MAX_ATTEMPTS_KEY, 201 DFSConfigKeys.DFS_HTTP_CLIENT_RETRY_MAX_ATTEMPTS_DEFAULT); 202 int failoverSleepBaseMillis = conf.getInt( 203 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_BASE_KEY, 204 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_BASE_DEFAULT); 205 int failoverSleepMaxMillis = conf.getInt( 206 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_MAX_KEY, 207 DFSConfigKeys.DFS_HTTP_CLIENT_FAILOVER_SLEEPTIME_MAX_DEFAULT); 208 209 this.retryPolicy = RetryPolicies 210 .failoverOnNetworkException(RetryPolicies.TRY_ONCE_THEN_FAIL, 211 maxFailoverAttempts, maxRetryAttempts, failoverSleepBaseMillis, 212 failoverSleepMaxMillis); 213 } 214 215 this.workingDir = getHomeDirectory(); 216 217 if (UserGroupInformation.isSecurityEnabled()) { 218 tokenAspect.initDelegationToken(ugi); 219 } 220 } 221 222 @Override 223 public URI getCanonicalUri() { 224 return super.getCanonicalUri(); 225 } 226 227 /** Is WebHDFS enabled in conf? */ 228 public static boolean isEnabled(final Configuration conf, final Log log) { 229 final boolean b = conf.getBoolean(DFSConfigKeys.DFS_WEBHDFS_ENABLED_KEY, 230 DFSConfigKeys.DFS_WEBHDFS_ENABLED_DEFAULT); 231 return b; 232 } 233 234 protected synchronized Token<?> getDelegationToken() throws IOException { 235 tokenAspect.ensureTokenInitialized(); 236 return delegationToken; 237 } 238 239 @Override 240 protected int getDefaultPort() { 241 return getConf().getInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY, 242 DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT); 243 } 244 245 @Override 246 public URI getUri() { 247 return this.uri; 248 } 249 250 @Override 251 protected URI canonicalizeUri(URI uri) { 252 return NetUtils.getCanonicalUri(uri, getDefaultPort()); 253 } 254 255 /** @return the home directory. */ 256 public static String getHomeDirectoryString(final UserGroupInformation ugi) { 257 return "/user/" + ugi.getShortUserName(); 258 } 259 260 @Override 261 public Path getHomeDirectory() { 262 return makeQualified(new Path(getHomeDirectoryString(ugi))); 263 } 264 265 @Override 266 public synchronized Path getWorkingDirectory() { 267 return workingDir; 268 } 269 270 @Override 271 public synchronized void setWorkingDirectory(final Path dir) { 272 String result = makeAbsolute(dir).toUri().getPath(); 273 if (!DFSUtil.isValidName(result)) { 274 throw new IllegalArgumentException("Invalid DFS directory name " + 275 result); 276 } 277 workingDir = makeAbsolute(dir); 278 } 279 280 private Path makeAbsolute(Path f) { 281 return f.isAbsolute()? f: new Path(workingDir, f); 282 } 283 284 static Map<?, ?> jsonParse(final HttpURLConnection c, final boolean useErrorStream 285 ) throws IOException { 286 if (c.getContentLength() == 0) { 287 return null; 288 } 289 final InputStream in = useErrorStream? c.getErrorStream(): c.getInputStream(); 290 if (in == null) { 291 throw new IOException("The " + (useErrorStream? "error": "input") + " stream is null."); 292 } 293 final String contentType = c.getContentType(); 294 if (contentType != null) { 295 final MediaType parsed = MediaType.valueOf(contentType); 296 if (!MediaType.APPLICATION_JSON_TYPE.isCompatible(parsed)) { 297 throw new IOException("Content-Type \"" + contentType 298 + "\" is incompatible with \"" + MediaType.APPLICATION_JSON 299 + "\" (parsed=\"" + parsed + "\")"); 300 } 301 } 302 return (Map<?, ?>)JSON.parse(new InputStreamReader(in, Charsets.UTF_8)); 303 } 304 305 private static Map<?, ?> validateResponse(final HttpOpParam.Op op, 306 final HttpURLConnection conn, boolean unwrapException) throws IOException { 307 final int code = conn.getResponseCode(); 308 // server is demanding an authentication we don't support 309 if (code == HttpURLConnection.HTTP_UNAUTHORIZED) { 310 throw new IOException( 311 new AuthenticationException(conn.getResponseMessage())); 312 } 313 if (code != op.getExpectedHttpResponseCode()) { 314 final Map<?, ?> m; 315 try { 316 m = jsonParse(conn, true); 317 } catch(Exception e) { 318 throw new IOException("Unexpected HTTP response: code=" + code + " != " 319 + op.getExpectedHttpResponseCode() + ", " + op.toQueryString() 320 + ", message=" + conn.getResponseMessage(), e); 321 } 322 323 if (m == null) { 324 throw new IOException("Unexpected HTTP response: code=" + code + " != " 325 + op.getExpectedHttpResponseCode() + ", " + op.toQueryString() 326 + ", message=" + conn.getResponseMessage()); 327 } else if (m.get(RemoteException.class.getSimpleName()) == null) { 328 return m; 329 } 330 331 final RemoteException re = JsonUtil.toRemoteException(m); 332 throw unwrapException? toIOException(re): re; 333 } 334 return null; 335 } 336 337 /** 338 * Covert an exception to an IOException. 339 * 340 * For a non-IOException, wrap it with IOException. 341 * For a RemoteException, unwrap it. 342 * For an IOException which is not a RemoteException, return it. 343 */ 344 private static IOException toIOException(Exception e) { 345 if (!(e instanceof IOException)) { 346 return new IOException(e); 347 } 348 349 final IOException ioe = (IOException)e; 350 if (!(ioe instanceof RemoteException)) { 351 return ioe; 352 } 353 354 return ((RemoteException)ioe).unwrapRemoteException(); 355 } 356 357 private synchronized InetSocketAddress getCurrentNNAddr() { 358 return nnAddrs[currentNNAddrIndex]; 359 } 360 361 /** 362 * Reset the appropriate state to gracefully fail over to another name node 363 */ 364 private synchronized void resetStateToFailOver() { 365 currentNNAddrIndex = (currentNNAddrIndex + 1) % nnAddrs.length; 366 delegationToken = null; 367 tokenAspect.reset(); 368 } 369 370 /** 371 * Return a URL pointing to given path on the namenode. 372 * 373 * @param path to obtain the URL for 374 * @param query string to append to the path 375 * @return namenode URL referring to the given path 376 * @throws IOException on error constructing the URL 377 */ 378 private URL getNamenodeURL(String path, String query) throws IOException { 379 InetSocketAddress nnAddr = getCurrentNNAddr(); 380 final URL url = new URL(getTransportScheme(), nnAddr.getHostName(), 381 nnAddr.getPort(), path + '?' + query); 382 if (LOG.isTraceEnabled()) { 383 LOG.trace("url=" + url); 384 } 385 return url; 386 } 387 388 Param<?,?>[] getAuthParameters(final HttpOpParam.Op op) throws IOException { 389 List<Param<?,?>> authParams = Lists.newArrayList(); 390 // Skip adding delegation token for token operations because these 391 // operations require authentication. 392 Token<?> token = null; 393 if (UserGroupInformation.isSecurityEnabled() && !op.getRequireAuth()) { 394 token = getDelegationToken(); 395 } 396 if (token != null) { 397 authParams.add(new DelegationParam(token.encodeToUrlString())); 398 } else { 399 UserGroupInformation userUgi = ugi; 400 UserGroupInformation realUgi = userUgi.getRealUser(); 401 if (realUgi != null) { // proxy user 402 authParams.add(new DoAsParam(userUgi.getShortUserName())); 403 userUgi = realUgi; 404 } 405 authParams.add(new UserParam(userUgi.getShortUserName())); 406 } 407 return authParams.toArray(new Param<?,?>[0]); 408 } 409 410 URL toUrl(final HttpOpParam.Op op, final Path fspath, 411 final Param<?,?>... parameters) throws IOException { 412 //initialize URI path and query 413 final String path = PATH_PREFIX 414 + (fspath == null? "/": makeQualified(fspath).toUri().getRawPath()); 415 final String query = op.toQueryString() 416 + Param.toSortedString("&", getAuthParameters(op)) 417 + Param.toSortedString("&", parameters); 418 final URL url = getNamenodeURL(path, query); 419 if (LOG.isTraceEnabled()) { 420 LOG.trace("url=" + url); 421 } 422 return url; 423 } 424 425 /** 426 * Run a http operation. 427 * Connect to the http server, validate response, and obtain the JSON output. 428 * 429 * @param op http operation 430 * @param fspath file system path 431 * @param parameters parameters for the operation 432 * @return a JSON object, e.g. Object[], Map<?, ?>, etc. 433 * @throws IOException 434 */ 435 private Map<?, ?> run(final HttpOpParam.Op op, final Path fspath, 436 final Param<?,?>... parameters) throws IOException { 437 return new FsPathRunner(op, fspath, parameters).run().json; 438 } 439 440 /** 441 * This class is for initialing a HTTP connection, connecting to server, 442 * obtaining a response, and also handling retry on failures. 443 */ 444 abstract class AbstractRunner { 445 abstract protected URL getUrl() throws IOException; 446 447 protected final HttpOpParam.Op op; 448 private final boolean redirected; 449 450 private boolean checkRetry; 451 protected HttpURLConnection conn = null; 452 private Map<?, ?> json = null; 453 454 protected AbstractRunner(final HttpOpParam.Op op, boolean redirected) { 455 this.op = op; 456 this.redirected = redirected; 457 } 458 459 AbstractRunner run() throws IOException { 460 UserGroupInformation connectUgi = ugi.getRealUser(); 461 if (connectUgi == null) { 462 connectUgi = ugi; 463 } 464 if (op.getRequireAuth()) { 465 connectUgi.checkTGTAndReloginFromKeytab(); 466 } 467 try { 468 // the entire lifecycle of the connection must be run inside the 469 // doAs to ensure authentication is performed correctly 470 return connectUgi.doAs( 471 new PrivilegedExceptionAction<AbstractRunner>() { 472 @Override 473 public AbstractRunner run() throws IOException { 474 return runWithRetry(); 475 } 476 }); 477 } catch (InterruptedException e) { 478 throw new IOException(e); 479 } 480 } 481 482 private void init() throws IOException { 483 checkRetry = !redirected; 484 URL url = getUrl(); 485 conn = (HttpURLConnection) connectionFactory.openConnection(url); 486 } 487 488 private void connect() throws IOException { 489 connect(op.getDoOutput()); 490 } 491 492 private void connect(boolean doOutput) throws IOException { 493 conn.setRequestMethod(op.getType().toString()); 494 conn.setDoOutput(doOutput); 495 conn.setInstanceFollowRedirects(false); 496 conn.connect(); 497 } 498 499 private void disconnect() { 500 if (conn != null) { 501 conn.disconnect(); 502 conn = null; 503 } 504 } 505 506 private AbstractRunner runWithRetry() throws IOException { 507 /** 508 * Do the real work. 509 * 510 * There are three cases that the code inside the loop can throw an 511 * IOException: 512 * 513 * <ul> 514 * <li>The connection has failed (e.g., ConnectException, 515 * @see FailoverOnNetworkExceptionRetry for more details)</li> 516 * <li>The namenode enters the standby state (i.e., StandbyException).</li> 517 * <li>The server returns errors for the command (i.e., RemoteException)</li> 518 * </ul> 519 * 520 * The call to shouldRetry() will conduct the retry policy. The policy 521 * examines the exception and swallows it if it decides to rerun the work. 522 */ 523 for(int retry = 0; ; retry++) { 524 try { 525 init(); 526 if (op.getDoOutput()) { 527 twoStepWrite(); 528 } else { 529 getResponse(op != GetOpParam.Op.OPEN); 530 } 531 return this; 532 } catch(IOException ioe) { 533 Throwable cause = ioe.getCause(); 534 if (cause != null && cause instanceof AuthenticationException) { 535 throw ioe; // no retries for auth failures 536 } 537 shouldRetry(ioe, retry); 538 } 539 } 540 } 541 542 private void shouldRetry(final IOException ioe, final int retry 543 ) throws IOException { 544 InetSocketAddress nnAddr = getCurrentNNAddr(); 545 if (checkRetry) { 546 try { 547 final RetryPolicy.RetryAction a = retryPolicy.shouldRetry( 548 ioe, retry, 0, true); 549 550 boolean isRetry = a.action == RetryPolicy.RetryAction.RetryDecision.RETRY; 551 boolean isFailoverAndRetry = 552 a.action == RetryPolicy.RetryAction.RetryDecision.FAILOVER_AND_RETRY; 553 554 if (isRetry || isFailoverAndRetry) { 555 LOG.info("Retrying connect to namenode: " + nnAddr 556 + ". Already tried " + retry + " time(s); retry policy is " 557 + retryPolicy + ", delay " + a.delayMillis + "ms."); 558 559 if (isFailoverAndRetry) { 560 resetStateToFailOver(); 561 } 562 563 Thread.sleep(a.delayMillis); 564 return; 565 } 566 } catch(Exception e) { 567 LOG.warn("Original exception is ", ioe); 568 throw toIOException(e); 569 } 570 } 571 throw toIOException(ioe); 572 } 573 574 /** 575 * Two-step Create/Append: 576 * Step 1) Submit a Http request with neither auto-redirect nor data. 577 * Step 2) Submit another Http request with the URL from the Location header with data. 578 * 579 * The reason of having two-step create/append is for preventing clients to 580 * send out the data before the redirect. This issue is addressed by the 581 * "Expect: 100-continue" header in HTTP/1.1; see RFC 2616, Section 8.2.3. 582 * Unfortunately, there are software library bugs (e.g. Jetty 6 http server 583 * and Java 6 http client), which do not correctly implement "Expect: 584 * 100-continue". The two-step create/append is a temporary workaround for 585 * the software library bugs. 586 */ 587 HttpURLConnection twoStepWrite() throws IOException { 588 //Step 1) Submit a Http request with neither auto-redirect nor data. 589 connect(false); 590 validateResponse(HttpOpParam.TemporaryRedirectOp.valueOf(op), conn, false); 591 final String redirect = conn.getHeaderField("Location"); 592 disconnect(); 593 checkRetry = false; 594 595 //Step 2) Submit another Http request with the URL from the Location header with data. 596 conn = (HttpURLConnection) connectionFactory.openConnection(new URL( 597 redirect)); 598 conn.setRequestProperty("Content-Type", 599 MediaType.APPLICATION_OCTET_STREAM); 600 conn.setChunkedStreamingMode(32 << 10); //32kB-chunk 601 connect(); 602 return conn; 603 } 604 605 FSDataOutputStream write(final int bufferSize) throws IOException { 606 return WebHdfsFileSystem.this.write(op, conn, bufferSize); 607 } 608 609 void getResponse(boolean getJsonAndDisconnect) throws IOException { 610 try { 611 connect(); 612 final int code = conn.getResponseCode(); 613 if (!redirected && op.getRedirect() 614 && code != op.getExpectedHttpResponseCode()) { 615 final String redirect = conn.getHeaderField("Location"); 616 json = validateResponse(HttpOpParam.TemporaryRedirectOp.valueOf(op), 617 conn, false); 618 disconnect(); 619 620 checkRetry = false; 621 conn = (HttpURLConnection) connectionFactory.openConnection(new URL( 622 redirect)); 623 connect(); 624 } 625 626 json = validateResponse(op, conn, false); 627 if (json == null && getJsonAndDisconnect) { 628 json = jsonParse(conn, false); 629 } 630 } finally { 631 if (getJsonAndDisconnect) { 632 disconnect(); 633 } 634 } 635 } 636 } 637 638 final class FsPathRunner extends AbstractRunner { 639 private final Path fspath; 640 private final Param<?, ?>[] parameters; 641 642 FsPathRunner(final HttpOpParam.Op op, final Path fspath, final Param<?,?>... parameters) { 643 super(op, false); 644 this.fspath = fspath; 645 this.parameters = parameters; 646 } 647 648 @Override 649 protected URL getUrl() throws IOException { 650 return toUrl(op, fspath, parameters); 651 } 652 } 653 654 final class URLRunner extends AbstractRunner { 655 private final URL url; 656 @Override 657 protected URL getUrl() { 658 return url; 659 } 660 661 protected URLRunner(final HttpOpParam.Op op, final URL url, boolean redirected) { 662 super(op, redirected); 663 this.url = url; 664 } 665 } 666 667 private FsPermission applyUMask(FsPermission permission) { 668 if (permission == null) { 669 permission = FsPermission.getDefault(); 670 } 671 return permission.applyUMask(FsPermission.getUMask(getConf())); 672 } 673 674 private HdfsFileStatus getHdfsFileStatus(Path f) throws IOException { 675 final HttpOpParam.Op op = GetOpParam.Op.GETFILESTATUS; 676 final Map<?, ?> json = run(op, f); 677 final HdfsFileStatus status = JsonUtil.toFileStatus(json, true); 678 if (status == null) { 679 throw new FileNotFoundException("File does not exist: " + f); 680 } 681 return status; 682 } 683 684 @Override 685 public FileStatus getFileStatus(Path f) throws IOException { 686 statistics.incrementReadOps(1); 687 return makeQualified(getHdfsFileStatus(f), f); 688 } 689 690 private FileStatus makeQualified(HdfsFileStatus f, Path parent) { 691 return new FileStatus(f.getLen(), f.isDir(), f.getReplication(), 692 f.getBlockSize(), f.getModificationTime(), f.getAccessTime(), 693 f.getPermission(), f.getOwner(), f.getGroup(), 694 f.isSymlink() ? new Path(f.getSymlink()) : null, 695 f.getFullPath(parent).makeQualified(getUri(), getWorkingDirectory())); 696 } 697 698 @Override 699 public AclStatus getAclStatus(Path f) throws IOException { 700 final HttpOpParam.Op op = GetOpParam.Op.GETACLSTATUS; 701 final Map<?, ?> json = run(op, f); 702 AclStatus status = JsonUtil.toAclStatus(json); 703 if (status == null) { 704 throw new FileNotFoundException("File does not exist: " + f); 705 } 706 return status; 707 } 708 709 @Override 710 public boolean mkdirs(Path f, FsPermission permission) throws IOException { 711 statistics.incrementWriteOps(1); 712 final HttpOpParam.Op op = PutOpParam.Op.MKDIRS; 713 final Map<?, ?> json = run(op, f, 714 new PermissionParam(applyUMask(permission))); 715 return (Boolean)json.get("boolean"); 716 } 717 718 /** 719 * Create a symlink pointing to the destination path. 720 * @see org.apache.hadoop.fs.Hdfs#createSymlink(Path, Path, boolean) 721 */ 722 public void createSymlink(Path destination, Path f, boolean createParent 723 ) throws IOException { 724 statistics.incrementWriteOps(1); 725 final HttpOpParam.Op op = PutOpParam.Op.CREATESYMLINK; 726 run(op, f, new DestinationParam(makeQualified(destination).toUri().getPath()), 727 new CreateParentParam(createParent)); 728 } 729 730 @Override 731 public boolean rename(final Path src, final Path dst) throws IOException { 732 statistics.incrementWriteOps(1); 733 final HttpOpParam.Op op = PutOpParam.Op.RENAME; 734 final Map<?, ?> json = run(op, src, 735 new DestinationParam(makeQualified(dst).toUri().getPath())); 736 return (Boolean)json.get("boolean"); 737 } 738 739 @SuppressWarnings("deprecation") 740 @Override 741 public void rename(final Path src, final Path dst, 742 final Options.Rename... options) throws IOException { 743 statistics.incrementWriteOps(1); 744 final HttpOpParam.Op op = PutOpParam.Op.RENAME; 745 run(op, src, new DestinationParam(makeQualified(dst).toUri().getPath()), 746 new RenameOptionSetParam(options)); 747 } 748 749 @Override 750 public void setOwner(final Path p, final String owner, final String group 751 ) throws IOException { 752 if (owner == null && group == null) { 753 throw new IOException("owner == null && group == null"); 754 } 755 756 statistics.incrementWriteOps(1); 757 final HttpOpParam.Op op = PutOpParam.Op.SETOWNER; 758 run(op, p, new OwnerParam(owner), new GroupParam(group)); 759 } 760 761 @Override 762 public void setPermission(final Path p, final FsPermission permission 763 ) throws IOException { 764 statistics.incrementWriteOps(1); 765 final HttpOpParam.Op op = PutOpParam.Op.SETPERMISSION; 766 run(op, p, new PermissionParam(permission)); 767 } 768 769 @Override 770 public void modifyAclEntries(Path path, List<AclEntry> aclSpec) 771 throws IOException { 772 statistics.incrementWriteOps(1); 773 final HttpOpParam.Op op = PutOpParam.Op.MODIFYACLENTRIES; 774 run(op, path, new AclPermissionParam(aclSpec)); 775 } 776 777 @Override 778 public void removeAclEntries(Path path, List<AclEntry> aclSpec) 779 throws IOException { 780 statistics.incrementWriteOps(1); 781 final HttpOpParam.Op op = PutOpParam.Op.REMOVEACLENTRIES; 782 run(op, path, new AclPermissionParam(aclSpec)); 783 } 784 785 @Override 786 public void removeDefaultAcl(Path path) throws IOException { 787 statistics.incrementWriteOps(1); 788 final HttpOpParam.Op op = PutOpParam.Op.REMOVEDEFAULTACL; 789 run(op, path); 790 } 791 792 @Override 793 public void removeAcl(Path path) throws IOException { 794 statistics.incrementWriteOps(1); 795 final HttpOpParam.Op op = PutOpParam.Op.REMOVEACL; 796 run(op, path); 797 } 798 799 @Override 800 public void setAcl(final Path p, final List<AclEntry> aclSpec) 801 throws IOException { 802 statistics.incrementWriteOps(1); 803 final HttpOpParam.Op op = PutOpParam.Op.SETACL; 804 run(op, p, new AclPermissionParam(aclSpec)); 805 } 806 807 @Override 808 public boolean setReplication(final Path p, final short replication 809 ) throws IOException { 810 statistics.incrementWriteOps(1); 811 final HttpOpParam.Op op = PutOpParam.Op.SETREPLICATION; 812 final Map<?, ?> json = run(op, p, new ReplicationParam(replication)); 813 return (Boolean)json.get("boolean"); 814 } 815 816 @Override 817 public void setTimes(final Path p, final long mtime, final long atime 818 ) throws IOException { 819 statistics.incrementWriteOps(1); 820 final HttpOpParam.Op op = PutOpParam.Op.SETTIMES; 821 run(op, p, new ModificationTimeParam(mtime), new AccessTimeParam(atime)); 822 } 823 824 @Override 825 public long getDefaultBlockSize() { 826 return getConf().getLongBytes(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, 827 DFSConfigKeys.DFS_BLOCK_SIZE_DEFAULT); 828 } 829 830 @Override 831 public short getDefaultReplication() { 832 return (short)getConf().getInt(DFSConfigKeys.DFS_REPLICATION_KEY, 833 DFSConfigKeys.DFS_REPLICATION_DEFAULT); 834 } 835 836 FSDataOutputStream write(final HttpOpParam.Op op, 837 final HttpURLConnection conn, final int bufferSize) throws IOException { 838 return new FSDataOutputStream(new BufferedOutputStream( 839 conn.getOutputStream(), bufferSize), statistics) { 840 @Override 841 public void close() throws IOException { 842 try { 843 super.close(); 844 } finally { 845 try { 846 validateResponse(op, conn, true); 847 } finally { 848 conn.disconnect(); 849 } 850 } 851 } 852 }; 853 } 854 855 @Override 856 public void concat(final Path trg, final Path [] srcs) throws IOException { 857 statistics.incrementWriteOps(1); 858 final HttpOpParam.Op op = PostOpParam.Op.CONCAT; 859 860 ConcatSourcesParam param = new ConcatSourcesParam(srcs); 861 run(op, trg, param); 862 } 863 864 @Override 865 public FSDataOutputStream create(final Path f, final FsPermission permission, 866 final boolean overwrite, final int bufferSize, final short replication, 867 final long blockSize, final Progressable progress) throws IOException { 868 statistics.incrementWriteOps(1); 869 870 final HttpOpParam.Op op = PutOpParam.Op.CREATE; 871 return new FsPathRunner(op, f, 872 new PermissionParam(applyUMask(permission)), 873 new OverwriteParam(overwrite), 874 new BufferSizeParam(bufferSize), 875 new ReplicationParam(replication), 876 new BlockSizeParam(blockSize)) 877 .run() 878 .write(bufferSize); 879 } 880 881 @Override 882 public FSDataOutputStream append(final Path f, final int bufferSize, 883 final Progressable progress) throws IOException { 884 statistics.incrementWriteOps(1); 885 886 final HttpOpParam.Op op = PostOpParam.Op.APPEND; 887 return new FsPathRunner(op, f, new BufferSizeParam(bufferSize)) 888 .run() 889 .write(bufferSize); 890 } 891 892 @Override 893 public boolean delete(Path f, boolean recursive) throws IOException { 894 final HttpOpParam.Op op = DeleteOpParam.Op.DELETE; 895 final Map<?, ?> json = run(op, f, new RecursiveParam(recursive)); 896 return (Boolean)json.get("boolean"); 897 } 898 899 @Override 900 public FSDataInputStream open(final Path f, final int buffersize 901 ) throws IOException { 902 statistics.incrementReadOps(1); 903 final HttpOpParam.Op op = GetOpParam.Op.OPEN; 904 final URL url = toUrl(op, f, new BufferSizeParam(buffersize)); 905 return new FSDataInputStream(new OffsetUrlInputStream( 906 new OffsetUrlOpener(url), new OffsetUrlOpener(null))); 907 } 908 909 @Override 910 public void close() throws IOException { 911 super.close(); 912 synchronized (this) { 913 tokenAspect.removeRenewAction(); 914 } 915 } 916 917 class OffsetUrlOpener extends ByteRangeInputStream.URLOpener { 918 OffsetUrlOpener(final URL url) { 919 super(url); 920 } 921 922 /** Setup offset url and connect. */ 923 @Override 924 protected HttpURLConnection connect(final long offset, 925 final boolean resolved) throws IOException { 926 final URL offsetUrl = offset == 0L? url 927 : new URL(url + "&" + new OffsetParam(offset)); 928 return new URLRunner(GetOpParam.Op.OPEN, offsetUrl, resolved).run().conn; 929 } 930 } 931 932 private static final String OFFSET_PARAM_PREFIX = OffsetParam.NAME + "="; 933 934 /** Remove offset parameter, if there is any, from the url */ 935 static URL removeOffsetParam(final URL url) throws MalformedURLException { 936 String query = url.getQuery(); 937 if (query == null) { 938 return url; 939 } 940 final String lower = query.toLowerCase(); 941 if (!lower.startsWith(OFFSET_PARAM_PREFIX) 942 && !lower.contains("&" + OFFSET_PARAM_PREFIX)) { 943 return url; 944 } 945 946 //rebuild query 947 StringBuilder b = null; 948 for(final StringTokenizer st = new StringTokenizer(query, "&"); 949 st.hasMoreTokens();) { 950 final String token = st.nextToken(); 951 if (!token.toLowerCase().startsWith(OFFSET_PARAM_PREFIX)) { 952 if (b == null) { 953 b = new StringBuilder("?").append(token); 954 } else { 955 b.append('&').append(token); 956 } 957 } 958 } 959 query = b == null? "": b.toString(); 960 961 final String urlStr = url.toString(); 962 return new URL(urlStr.substring(0, urlStr.indexOf('?')) + query); 963 } 964 965 static class OffsetUrlInputStream extends ByteRangeInputStream { 966 OffsetUrlInputStream(OffsetUrlOpener o, OffsetUrlOpener r) { 967 super(o, r); 968 } 969 970 /** Remove offset parameter before returning the resolved url. */ 971 @Override 972 protected URL getResolvedUrl(final HttpURLConnection connection 973 ) throws MalformedURLException { 974 return removeOffsetParam(connection.getURL()); 975 } 976 } 977 978 @Override 979 public FileStatus[] listStatus(final Path f) throws IOException { 980 statistics.incrementReadOps(1); 981 982 final HttpOpParam.Op op = GetOpParam.Op.LISTSTATUS; 983 final Map<?, ?> json = run(op, f); 984 final Map<?, ?> rootmap = (Map<?, ?>)json.get(FileStatus.class.getSimpleName() + "es"); 985 final Object[] array = (Object[])rootmap.get(FileStatus.class.getSimpleName()); 986 987 //convert FileStatus 988 final FileStatus[] statuses = new FileStatus[array.length]; 989 for(int i = 0; i < array.length; i++) { 990 final Map<?, ?> m = (Map<?, ?>)array[i]; 991 statuses[i] = makeQualified(JsonUtil.toFileStatus(m, false), f); 992 } 993 return statuses; 994 } 995 996 @Override 997 public Token<DelegationTokenIdentifier> getDelegationToken( 998 final String renewer) throws IOException { 999 final HttpOpParam.Op op = GetOpParam.Op.GETDELEGATIONTOKEN; 1000 final Map<?, ?> m = run(op, null, new RenewerParam(renewer)); 1001 final Token<DelegationTokenIdentifier> token = JsonUtil.toDelegationToken(m); 1002 token.setService(tokenServiceName); 1003 return token; 1004 } 1005 1006 @Override 1007 public synchronized Token<?> getRenewToken() { 1008 return delegationToken; 1009 } 1010 1011 @Override 1012 public <T extends TokenIdentifier> void setDelegationToken( 1013 final Token<T> token) { 1014 synchronized (this) { 1015 delegationToken = token; 1016 } 1017 } 1018 1019 @Override 1020 public synchronized long renewDelegationToken(final Token<?> token 1021 ) throws IOException { 1022 final HttpOpParam.Op op = PutOpParam.Op.RENEWDELEGATIONTOKEN; 1023 TokenArgumentParam dtargParam = new TokenArgumentParam( 1024 token.encodeToUrlString()); 1025 final Map<?, ?> m = run(op, null, dtargParam); 1026 return (Long) m.get("long"); 1027 } 1028 1029 @Override 1030 public synchronized void cancelDelegationToken(final Token<?> token 1031 ) throws IOException { 1032 final HttpOpParam.Op op = PutOpParam.Op.CANCELDELEGATIONTOKEN; 1033 TokenArgumentParam dtargParam = new TokenArgumentParam( 1034 token.encodeToUrlString()); 1035 run(op, null, dtargParam); 1036 } 1037 1038 @Override 1039 public BlockLocation[] getFileBlockLocations(final FileStatus status, 1040 final long offset, final long length) throws IOException { 1041 if (status == null) { 1042 return null; 1043 } 1044 return getFileBlockLocations(status.getPath(), offset, length); 1045 } 1046 1047 @Override 1048 public BlockLocation[] getFileBlockLocations(final Path p, 1049 final long offset, final long length) throws IOException { 1050 statistics.incrementReadOps(1); 1051 1052 final HttpOpParam.Op op = GetOpParam.Op.GET_BLOCK_LOCATIONS; 1053 final Map<?, ?> m = run(op, p, new OffsetParam(offset), 1054 new LengthParam(length)); 1055 return DFSUtil.locatedBlocks2Locations(JsonUtil.toLocatedBlocks(m)); 1056 } 1057 1058 @Override 1059 public ContentSummary getContentSummary(final Path p) throws IOException { 1060 statistics.incrementReadOps(1); 1061 1062 final HttpOpParam.Op op = GetOpParam.Op.GETCONTENTSUMMARY; 1063 final Map<?, ?> m = run(op, p); 1064 return JsonUtil.toContentSummary(m); 1065 } 1066 1067 @Override 1068 public MD5MD5CRC32FileChecksum getFileChecksum(final Path p 1069 ) throws IOException { 1070 statistics.incrementReadOps(1); 1071 1072 final HttpOpParam.Op op = GetOpParam.Op.GETFILECHECKSUM; 1073 final Map<?, ?> m = run(op, p); 1074 return JsonUtil.toMD5MD5CRC32FileChecksum(m); 1075 } 1076 1077 @Override 1078 public String getCanonicalServiceName() { 1079 return tokenServiceName == null ? super.getCanonicalServiceName() 1080 : tokenServiceName.toString(); 1081 } 1082 1083 @VisibleForTesting 1084 InetSocketAddress[] getResolvedNNAddr() { 1085 return nnAddrs; 1086 } 1087 }