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.security.PrivilegedExceptionAction; 022 023 import java.io.BufferedOutputStream; 024 import java.io.FileNotFoundException; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.io.InputStreamReader; 028 import java.net.HttpURLConnection; 029 import java.net.InetSocketAddress; 030 import java.net.MalformedURLException; 031 import java.net.URI; 032 import java.net.URISyntaxException; 033 import java.net.URL; 034 import java.util.Collection; 035 import java.util.List; 036 import java.util.Map; 037 import java.util.StringTokenizer; 038 039 import javax.ws.rs.core.MediaType; 040 041 import org.apache.commons.logging.Log; 042 import org.apache.commons.logging.LogFactory; 043 import org.apache.hadoop.conf.Configuration; 044 import org.apache.hadoop.fs.BlockLocation; 045 import org.apache.hadoop.fs.ContentSummary; 046 import org.apache.hadoop.fs.FSDataInputStream; 047 import org.apache.hadoop.fs.FSDataOutputStream; 048 import org.apache.hadoop.fs.FileAlreadyExistsException; 049 import org.apache.hadoop.fs.FileStatus; 050 import org.apache.hadoop.fs.FileSystem; 051 import org.apache.hadoop.fs.MD5MD5CRC32FileChecksum; 052 import org.apache.hadoop.fs.Options; 053 import org.apache.hadoop.fs.ParentNotDirectoryException; 054 import org.apache.hadoop.fs.Path; 055 import org.apache.hadoop.fs.permission.FsPermission; 056 import org.apache.hadoop.hdfs.ByteRangeInputStream; 057 import org.apache.hadoop.hdfs.DFSConfigKeys; 058 import org.apache.hadoop.hdfs.DFSUtil; 059 import org.apache.hadoop.hdfs.protocol.DSQuotaExceededException; 060 import org.apache.hadoop.hdfs.protocol.HdfsFileStatus; 061 import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException; 062 import org.apache.hadoop.hdfs.protocol.UnresolvedPathException; 063 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; 064 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenRenewer; 065 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSelector; 066 import org.apache.hadoop.hdfs.server.common.JspHelper; 067 import org.apache.hadoop.hdfs.server.namenode.SafeModeException; 068 import org.apache.hadoop.hdfs.web.resources.*; 069 import org.apache.hadoop.io.Text; 070 import org.apache.hadoop.ipc.RemoteException; 071 import org.apache.hadoop.net.NetUtils; 072 import org.apache.hadoop.security.AccessControlException; 073 import org.apache.hadoop.security.SecurityUtil; 074 import org.apache.hadoop.security.UserGroupInformation; 075 import org.apache.hadoop.security.authentication.client.AuthenticatedURL; 076 import org.apache.hadoop.security.authentication.client.AuthenticationException; 077 import org.apache.hadoop.security.authorize.AuthorizationException; 078 import org.apache.hadoop.security.token.SecretManager.InvalidToken; 079 import org.apache.hadoop.security.token.Token; 080 import org.apache.hadoop.security.token.TokenIdentifier; 081 import org.apache.hadoop.security.token.TokenRenewer; 082 import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSelector; 083 import org.apache.hadoop.util.Progressable; 084 import org.mortbay.util.ajax.JSON; 085 086 import com.google.common.annotations.VisibleForTesting; 087 import com.google.common.collect.Lists; 088 089 /** A FileSystem for HDFS over the web. */ 090 public class WebHdfsFileSystem extends FileSystem 091 implements DelegationTokenRenewer.Renewable { 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 /** SPNEGO authenticator */ 101 private static final KerberosUgiAuthenticator AUTH = new KerberosUgiAuthenticator(); 102 /** Delegation token kind */ 103 public static final Text TOKEN_KIND = new Text("WEBHDFS delegation"); 104 /** Token selector */ 105 public static final WebHdfsDelegationTokenSelector DT_SELECTOR 106 = new WebHdfsDelegationTokenSelector(); 107 108 private DelegationTokenRenewer dtRenewer = null; 109 @VisibleForTesting 110 DelegationTokenRenewer.RenewAction<?> action; 111 112 @VisibleForTesting 113 protected synchronized void addRenewAction(final WebHdfsFileSystem webhdfs) { 114 if (dtRenewer == null) { 115 dtRenewer = DelegationTokenRenewer.getInstance(); 116 } 117 118 action = dtRenewer.addRenewAction(webhdfs); 119 } 120 121 /** Is WebHDFS enabled in conf? */ 122 public static boolean isEnabled(final Configuration conf, final Log log) { 123 final boolean b = conf.getBoolean(DFSConfigKeys.DFS_WEBHDFS_ENABLED_KEY, 124 DFSConfigKeys.DFS_WEBHDFS_ENABLED_DEFAULT); 125 log.info(DFSConfigKeys.DFS_WEBHDFS_ENABLED_KEY + " = " + b); 126 return b; 127 } 128 129 private UserGroupInformation ugi; 130 private InetSocketAddress nnAddr; 131 private URI uri; 132 private boolean hasInitedToken; 133 private Token<?> delegationToken; 134 private Path workingDir; 135 136 @Override 137 public synchronized void initialize(URI uri, Configuration conf 138 ) throws IOException { 139 super.initialize(uri, conf); 140 setConf(conf); 141 ugi = UserGroupInformation.getCurrentUser(); 142 try { 143 this.uri = new URI(uri.getScheme(), uri.getAuthority(), null, null, null); 144 } catch (URISyntaxException e) { 145 throw new IllegalArgumentException(e); 146 } 147 this.nnAddr = NetUtils.createSocketAddr(uri.getAuthority(), getDefaultPort()); 148 this.workingDir = getHomeDirectory(); 149 150 if (UserGroupInformation.isSecurityEnabled()) { 151 initDelegationToken(); 152 } 153 } 154 155 protected void initDelegationToken() throws IOException { 156 // look for webhdfs token, then try hdfs 157 Token<?> token = selectDelegationToken(ugi); 158 if (token != null) { 159 LOG.debug("Found existing DT for " + token.getService()); 160 setDelegationToken(token); 161 hasInitedToken = true; 162 } 163 } 164 165 protected synchronized Token<?> getDelegationToken() throws IOException { 166 // we haven't inited yet, or we used to have a token but it expired 167 if (!hasInitedToken || (action != null && !action.isValid())) { 168 //since we don't already have a token, go get one 169 Token<?> token = getDelegationToken(null); 170 // security might be disabled 171 if (token != null) { 172 setDelegationToken(token); 173 addRenewAction(this); 174 LOG.debug("Created new DT for " + token.getService()); 175 } 176 hasInitedToken = true; 177 } 178 return delegationToken; 179 } 180 181 protected Token<DelegationTokenIdentifier> selectDelegationToken( 182 UserGroupInformation ugi) { 183 return DT_SELECTOR.selectToken(getCanonicalUri(), ugi.getTokens(), getConf()); 184 } 185 186 @Override 187 protected int getDefaultPort() { 188 return getConf().getInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY, 189 DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT); 190 } 191 192 @Override 193 public URI getUri() { 194 return this.uri; 195 } 196 197 /** @return the home directory. */ 198 public static String getHomeDirectoryString(final UserGroupInformation ugi) { 199 return "/user/" + ugi.getShortUserName(); 200 } 201 202 @Override 203 public Path getHomeDirectory() { 204 return makeQualified(new Path(getHomeDirectoryString(ugi))); 205 } 206 207 @Override 208 public synchronized Path getWorkingDirectory() { 209 return workingDir; 210 } 211 212 @Override 213 public synchronized void setWorkingDirectory(final Path dir) { 214 String result = makeAbsolute(dir).toUri().getPath(); 215 if (!DFSUtil.isValidName(result)) { 216 throw new IllegalArgumentException("Invalid DFS directory name " + 217 result); 218 } 219 workingDir = makeAbsolute(dir); 220 } 221 222 private Path makeAbsolute(Path f) { 223 return f.isAbsolute()? f: new Path(workingDir, f); 224 } 225 226 static Map<?, ?> jsonParse(final HttpURLConnection c, final boolean useErrorStream 227 ) throws IOException { 228 if (c.getContentLength() == 0) { 229 return null; 230 } 231 final InputStream in = useErrorStream? c.getErrorStream(): c.getInputStream(); 232 if (in == null) { 233 throw new IOException("The " + (useErrorStream? "error": "input") + " stream is null."); 234 } 235 final String contentType = c.getContentType(); 236 if (contentType != null) { 237 final MediaType parsed = MediaType.valueOf(contentType); 238 if (!MediaType.APPLICATION_JSON_TYPE.isCompatible(parsed)) { 239 throw new IOException("Content-Type \"" + contentType 240 + "\" is incompatible with \"" + MediaType.APPLICATION_JSON 241 + "\" (parsed=\"" + parsed + "\")"); 242 } 243 } 244 return (Map<?, ?>)JSON.parse(new InputStreamReader(in)); 245 } 246 247 private static Map<?, ?> validateResponse(final HttpOpParam.Op op, 248 final HttpURLConnection conn) throws IOException { 249 final int code = conn.getResponseCode(); 250 if (code != op.getExpectedHttpResponseCode()) { 251 final Map<?, ?> m; 252 try { 253 m = jsonParse(conn, true); 254 } catch(IOException e) { 255 throw new IOException("Unexpected HTTP response: code=" + code + " != " 256 + op.getExpectedHttpResponseCode() + ", " + op.toQueryString() 257 + ", message=" + conn.getResponseMessage(), e); 258 } 259 260 if (m == null) { 261 throw new IOException("Unexpected HTTP response: code=" + code + " != " 262 + op.getExpectedHttpResponseCode() + ", " + op.toQueryString() 263 + ", message=" + conn.getResponseMessage()); 264 } else if (m.get(RemoteException.class.getSimpleName()) == null) { 265 return m; 266 } 267 268 final RemoteException re = JsonUtil.toRemoteException(m); 269 throw re.unwrapRemoteException(AccessControlException.class, 270 InvalidToken.class, 271 AuthenticationException.class, 272 AuthorizationException.class, 273 FileAlreadyExistsException.class, 274 FileNotFoundException.class, 275 ParentNotDirectoryException.class, 276 UnresolvedPathException.class, 277 SafeModeException.class, 278 DSQuotaExceededException.class, 279 NSQuotaExceededException.class); 280 } 281 return null; 282 } 283 284 /** 285 * Return a URL pointing to given path on the namenode. 286 * 287 * @param path to obtain the URL for 288 * @param query string to append to the path 289 * @return namenode URL referring to the given path 290 * @throws IOException on error constructing the URL 291 */ 292 private URL getNamenodeURL(String path, String query) throws IOException { 293 final URL url = new URL("http", nnAddr.getHostName(), 294 nnAddr.getPort(), path + '?' + query); 295 if (LOG.isTraceEnabled()) { 296 LOG.trace("url=" + url); 297 } 298 return url; 299 } 300 301 Param<?,?>[] getAuthParameters(final HttpOpParam.Op op) throws IOException { 302 List<Param<?,?>> authParams = Lists.newArrayList(); 303 // Skip adding delegation token for token operations because these 304 // operations require authentication. 305 Token<?> token = null; 306 if (UserGroupInformation.isSecurityEnabled() && !op.getRequireAuth()) { 307 token = getDelegationToken(); 308 } 309 if (token != null) { 310 authParams.add(new DelegationParam(token.encodeToUrlString())); 311 } else { 312 UserGroupInformation userUgi = ugi; 313 UserGroupInformation realUgi = userUgi.getRealUser(); 314 if (realUgi != null) { // proxy user 315 authParams.add(new DoAsParam(userUgi.getShortUserName())); 316 userUgi = realUgi; 317 } 318 authParams.add(new UserParam(userUgi.getShortUserName())); 319 } 320 return authParams.toArray(new Param<?,?>[0]); 321 } 322 323 URL toUrl(final HttpOpParam.Op op, final Path fspath, 324 final Param<?,?>... parameters) throws IOException { 325 //initialize URI path and query 326 final String path = PATH_PREFIX 327 + (fspath == null? "/": makeQualified(fspath).toUri().getPath()); 328 final String query = op.toQueryString() 329 + Param.toSortedString("&", getAuthParameters(op)) 330 + Param.toSortedString("&", parameters); 331 final URL url = getNamenodeURL(path, query); 332 if (LOG.isTraceEnabled()) { 333 LOG.trace("url=" + url); 334 } 335 return url; 336 } 337 338 private HttpURLConnection getHttpUrlConnection(final URL url, 339 final boolean requireAuth) 340 throws IOException { 341 UserGroupInformation connectUgi = ugi.getRealUser(); 342 if (connectUgi == null) { 343 connectUgi = ugi; 344 } 345 try { 346 return connectUgi.doAs( 347 new PrivilegedExceptionAction<HttpURLConnection>() { 348 @Override 349 public HttpURLConnection run() throws IOException { 350 return openHttpUrlConnection(url, requireAuth); 351 } 352 }); 353 } catch (InterruptedException e) { 354 throw new IOException(e); 355 } 356 } 357 358 private HttpURLConnection openHttpUrlConnection(URL url, boolean requireAuth) 359 throws IOException { 360 final HttpURLConnection conn; 361 try { 362 if (requireAuth) { 363 LOG.debug("open AuthenticatedURL connection"); 364 UserGroupInformation.getCurrentUser().checkTGTAndReloginFromKeytab(); 365 final AuthenticatedURL.Token authToken = new AuthenticatedURL.Token(); 366 conn = new AuthenticatedURL(AUTH).openConnection(url, authToken); 367 } else { 368 LOG.debug("open URL connection"); 369 conn = (HttpURLConnection)url.openConnection(); 370 } 371 } catch (AuthenticationException e) { 372 throw new IOException(e); 373 } 374 return conn; 375 } 376 377 private HttpURLConnection httpConnect(final HttpOpParam.Op op, final Path fspath, 378 final Param<?,?>... parameters) throws IOException { 379 final URL url = toUrl(op, fspath, parameters); 380 381 //connect and get response 382 HttpURLConnection conn = getHttpUrlConnection(url, op.getRequireAuth()); 383 try { 384 conn.setRequestMethod(op.getType().toString()); 385 if (op.getDoOutput()) { 386 conn = twoStepWrite(conn, op); 387 conn.setRequestProperty("Content-Type", "application/octet-stream"); 388 } 389 conn.setDoOutput(op.getDoOutput()); 390 conn.connect(); 391 return conn; 392 } catch (IOException e) { 393 conn.disconnect(); 394 throw e; 395 } 396 } 397 398 /** 399 * Two-step Create/Append: 400 * Step 1) Submit a Http request with neither auto-redirect nor data. 401 * Step 2) Submit another Http request with the URL from the Location header with data. 402 * 403 * The reason of having two-step create/append is for preventing clients to 404 * send out the data before the redirect. This issue is addressed by the 405 * "Expect: 100-continue" header in HTTP/1.1; see RFC 2616, Section 8.2.3. 406 * Unfortunately, there are software library bugs (e.g. Jetty 6 http server 407 * and Java 6 http client), which do not correctly implement "Expect: 408 * 100-continue". The two-step create/append is a temporary workaround for 409 * the software library bugs. 410 */ 411 static HttpURLConnection twoStepWrite(HttpURLConnection conn, 412 final HttpOpParam.Op op) throws IOException { 413 //Step 1) Submit a Http request with neither auto-redirect nor data. 414 conn.setInstanceFollowRedirects(false); 415 conn.setDoOutput(false); 416 conn.connect(); 417 validateResponse(HttpOpParam.TemporaryRedirectOp.valueOf(op), conn); 418 final String redirect = conn.getHeaderField("Location"); 419 conn.disconnect(); 420 421 //Step 2) Submit another Http request with the URL from the Location header with data. 422 conn = (HttpURLConnection)new URL(redirect).openConnection(); 423 conn.setRequestMethod(op.getType().toString()); 424 conn.setChunkedStreamingMode(32 << 10); //32kB-chunk 425 return conn; 426 } 427 428 /** 429 * Run a http operation. 430 * Connect to the http server, validate response, and obtain the JSON output. 431 * 432 * @param op http operation 433 * @param fspath file system path 434 * @param parameters parameters for the operation 435 * @return a JSON object, e.g. Object[], Map<?, ?>, etc. 436 * @throws IOException 437 */ 438 private Map<?, ?> run(final HttpOpParam.Op op, final Path fspath, 439 final Param<?,?>... parameters) throws IOException { 440 final HttpURLConnection conn = httpConnect(op, fspath, parameters); 441 try { 442 final Map<?, ?> m = validateResponse(op, conn); 443 return m != null? m: jsonParse(conn, false); 444 } finally { 445 conn.disconnect(); 446 } 447 } 448 449 private FsPermission applyUMask(FsPermission permission) { 450 if (permission == null) { 451 permission = FsPermission.getDefault(); 452 } 453 return permission.applyUMask(FsPermission.getUMask(getConf())); 454 } 455 456 private HdfsFileStatus getHdfsFileStatus(Path f) throws IOException { 457 final HttpOpParam.Op op = GetOpParam.Op.GETFILESTATUS; 458 final Map<?, ?> json = run(op, f); 459 final HdfsFileStatus status = JsonUtil.toFileStatus(json, true); 460 if (status == null) { 461 throw new FileNotFoundException("File does not exist: " + f); 462 } 463 return status; 464 } 465 466 @Override 467 public FileStatus getFileStatus(Path f) throws IOException { 468 statistics.incrementReadOps(1); 469 return makeQualified(getHdfsFileStatus(f), f); 470 } 471 472 private FileStatus makeQualified(HdfsFileStatus f, Path parent) { 473 return new FileStatus(f.getLen(), f.isDir(), f.getReplication(), 474 f.getBlockSize(), f.getModificationTime(), f.getAccessTime(), 475 f.getPermission(), f.getOwner(), f.getGroup(), 476 f.isSymlink() ? new Path(f.getSymlink()) : null, 477 f.getFullPath(parent).makeQualified(getUri(), getWorkingDirectory())); 478 } 479 480 @Override 481 public boolean mkdirs(Path f, FsPermission permission) throws IOException { 482 statistics.incrementWriteOps(1); 483 final HttpOpParam.Op op = PutOpParam.Op.MKDIRS; 484 final Map<?, ?> json = run(op, f, 485 new PermissionParam(applyUMask(permission))); 486 return (Boolean)json.get("boolean"); 487 } 488 489 /** 490 * Create a symlink pointing to the destination path. 491 * @see org.apache.hadoop.fs.Hdfs#createSymlink(Path, Path, boolean) 492 */ 493 public void createSymlink(Path destination, Path f, boolean createParent 494 ) throws IOException { 495 statistics.incrementWriteOps(1); 496 final HttpOpParam.Op op = PutOpParam.Op.CREATESYMLINK; 497 run(op, f, new DestinationParam(makeQualified(destination).toUri().getPath()), 498 new CreateParentParam(createParent)); 499 } 500 501 @Override 502 public boolean rename(final Path src, final Path dst) throws IOException { 503 statistics.incrementWriteOps(1); 504 final HttpOpParam.Op op = PutOpParam.Op.RENAME; 505 final Map<?, ?> json = run(op, src, 506 new DestinationParam(makeQualified(dst).toUri().getPath())); 507 return (Boolean)json.get("boolean"); 508 } 509 510 @SuppressWarnings("deprecation") 511 @Override 512 public void rename(final Path src, final Path dst, 513 final Options.Rename... options) throws IOException { 514 statistics.incrementWriteOps(1); 515 final HttpOpParam.Op op = PutOpParam.Op.RENAME; 516 run(op, src, new DestinationParam(makeQualified(dst).toUri().getPath()), 517 new RenameOptionSetParam(options)); 518 } 519 520 @Override 521 public void setOwner(final Path p, final String owner, final String group 522 ) throws IOException { 523 if (owner == null && group == null) { 524 throw new IOException("owner == null && group == null"); 525 } 526 527 statistics.incrementWriteOps(1); 528 final HttpOpParam.Op op = PutOpParam.Op.SETOWNER; 529 run(op, p, new OwnerParam(owner), new GroupParam(group)); 530 } 531 532 @Override 533 public void setPermission(final Path p, final FsPermission permission 534 ) throws IOException { 535 statistics.incrementWriteOps(1); 536 final HttpOpParam.Op op = PutOpParam.Op.SETPERMISSION; 537 run(op, p, new PermissionParam(permission)); 538 } 539 540 @Override 541 public boolean setReplication(final Path p, final short replication 542 ) throws IOException { 543 statistics.incrementWriteOps(1); 544 final HttpOpParam.Op op = PutOpParam.Op.SETREPLICATION; 545 final Map<?, ?> json = run(op, p, new ReplicationParam(replication)); 546 return (Boolean)json.get("boolean"); 547 } 548 549 @Override 550 public void setTimes(final Path p, final long mtime, final long atime 551 ) throws IOException { 552 statistics.incrementWriteOps(1); 553 final HttpOpParam.Op op = PutOpParam.Op.SETTIMES; 554 run(op, p, new ModificationTimeParam(mtime), new AccessTimeParam(atime)); 555 } 556 557 @Override 558 public long getDefaultBlockSize() { 559 return getConf().getLongBytes(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, 560 DFSConfigKeys.DFS_BLOCK_SIZE_DEFAULT); 561 } 562 563 @Override 564 public short getDefaultReplication() { 565 return (short)getConf().getInt(DFSConfigKeys.DFS_REPLICATION_KEY, 566 DFSConfigKeys.DFS_REPLICATION_DEFAULT); 567 } 568 569 FSDataOutputStream write(final HttpOpParam.Op op, 570 final HttpURLConnection conn, final int bufferSize) throws IOException { 571 return new FSDataOutputStream(new BufferedOutputStream( 572 conn.getOutputStream(), bufferSize), statistics) { 573 @Override 574 public void close() throws IOException { 575 try { 576 super.close(); 577 } finally { 578 try { 579 validateResponse(op, conn); 580 } finally { 581 conn.disconnect(); 582 } 583 } 584 } 585 }; 586 } 587 588 @Override 589 public FSDataOutputStream create(final Path f, final FsPermission permission, 590 final boolean overwrite, final int bufferSize, final short replication, 591 final long blockSize, final Progressable progress) throws IOException { 592 statistics.incrementWriteOps(1); 593 594 final HttpOpParam.Op op = PutOpParam.Op.CREATE; 595 final HttpURLConnection conn = httpConnect(op, f, 596 new PermissionParam(applyUMask(permission)), 597 new OverwriteParam(overwrite), 598 new BufferSizeParam(bufferSize), 599 new ReplicationParam(replication), 600 new BlockSizeParam(blockSize)); 601 return write(op, conn, bufferSize); 602 } 603 604 @Override 605 public FSDataOutputStream append(final Path f, final int bufferSize, 606 final Progressable progress) throws IOException { 607 statistics.incrementWriteOps(1); 608 609 final HttpOpParam.Op op = PostOpParam.Op.APPEND; 610 final HttpURLConnection conn = httpConnect(op, f, 611 new BufferSizeParam(bufferSize)); 612 return write(op, conn, bufferSize); 613 } 614 615 @SuppressWarnings("deprecation") 616 @Override 617 public boolean delete(final Path f) throws IOException { 618 return delete(f, true); 619 } 620 621 @Override 622 public boolean delete(Path f, boolean recursive) throws IOException { 623 final HttpOpParam.Op op = DeleteOpParam.Op.DELETE; 624 final Map<?, ?> json = run(op, f, new RecursiveParam(recursive)); 625 return (Boolean)json.get("boolean"); 626 } 627 628 @Override 629 public FSDataInputStream open(final Path f, final int buffersize 630 ) throws IOException { 631 statistics.incrementReadOps(1); 632 final HttpOpParam.Op op = GetOpParam.Op.OPEN; 633 final URL url = toUrl(op, f, new BufferSizeParam(buffersize)); 634 return new FSDataInputStream(new OffsetUrlInputStream( 635 new OffsetUrlOpener(url), new OffsetUrlOpener(null))); 636 } 637 638 class OffsetUrlOpener extends ByteRangeInputStream.URLOpener { 639 /** The url with offset parameter */ 640 private URL offsetUrl; 641 642 OffsetUrlOpener(final URL url) { 643 super(url); 644 } 645 646 /** Open connection with offset url. */ 647 @Override 648 protected HttpURLConnection openConnection() throws IOException { 649 return getHttpUrlConnection(offsetUrl, false); 650 } 651 652 /** Setup offset url before open connection. */ 653 @Override 654 protected HttpURLConnection openConnection(final long offset) throws IOException { 655 offsetUrl = offset == 0L? url: new URL(url + "&" + new OffsetParam(offset)); 656 final HttpURLConnection conn = openConnection(); 657 conn.setRequestMethod("GET"); 658 return conn; 659 } 660 } 661 662 private static final String OFFSET_PARAM_PREFIX = OffsetParam.NAME + "="; 663 664 /** Remove offset parameter, if there is any, from the url */ 665 static URL removeOffsetParam(final URL url) throws MalformedURLException { 666 String query = url.getQuery(); 667 if (query == null) { 668 return url; 669 } 670 final String lower = query.toLowerCase(); 671 if (!lower.startsWith(OFFSET_PARAM_PREFIX) 672 && !lower.contains("&" + OFFSET_PARAM_PREFIX)) { 673 return url; 674 } 675 676 //rebuild query 677 StringBuilder b = null; 678 for(final StringTokenizer st = new StringTokenizer(query, "&"); 679 st.hasMoreTokens();) { 680 final String token = st.nextToken(); 681 if (!token.toLowerCase().startsWith(OFFSET_PARAM_PREFIX)) { 682 if (b == null) { 683 b = new StringBuilder("?").append(token); 684 } else { 685 b.append('&').append(token); 686 } 687 } 688 } 689 query = b == null? "": b.toString(); 690 691 final String urlStr = url.toString(); 692 return new URL(urlStr.substring(0, urlStr.indexOf('?')) + query); 693 } 694 695 static class OffsetUrlInputStream extends ByteRangeInputStream { 696 OffsetUrlInputStream(OffsetUrlOpener o, OffsetUrlOpener r) { 697 super(o, r); 698 } 699 700 @Override 701 protected void checkResponseCode(final HttpURLConnection connection 702 ) throws IOException { 703 validateResponse(GetOpParam.Op.OPEN, connection); 704 } 705 706 /** Remove offset parameter before returning the resolved url. */ 707 @Override 708 protected URL getResolvedUrl(final HttpURLConnection connection 709 ) throws MalformedURLException { 710 return removeOffsetParam(connection.getURL()); 711 } 712 } 713 714 @Override 715 public FileStatus[] listStatus(final Path f) throws IOException { 716 statistics.incrementReadOps(1); 717 718 final HttpOpParam.Op op = GetOpParam.Op.LISTSTATUS; 719 final Map<?, ?> json = run(op, f); 720 final Map<?, ?> rootmap = (Map<?, ?>)json.get(FileStatus.class.getSimpleName() + "es"); 721 final Object[] array = (Object[])rootmap.get(FileStatus.class.getSimpleName()); 722 723 //convert FileStatus 724 final FileStatus[] statuses = new FileStatus[array.length]; 725 for(int i = 0; i < array.length; i++) { 726 final Map<?, ?> m = (Map<?, ?>)array[i]; 727 statuses[i] = makeQualified(JsonUtil.toFileStatus(m, false), f); 728 } 729 return statuses; 730 } 731 732 @Override 733 public Token<DelegationTokenIdentifier> getDelegationToken( 734 final String renewer) throws IOException { 735 final HttpOpParam.Op op = GetOpParam.Op.GETDELEGATIONTOKEN; 736 final Map<?, ?> m = run(op, null, new RenewerParam(renewer)); 737 final Token<DelegationTokenIdentifier> token = JsonUtil.toDelegationToken(m); 738 SecurityUtil.setTokenService(token, nnAddr); 739 return token; 740 } 741 742 @Override 743 public Token<?> getRenewToken() { 744 return delegationToken; 745 } 746 747 @Override 748 public <T extends TokenIdentifier> void setDelegationToken( 749 final Token<T> token) { 750 synchronized(this) { 751 delegationToken = token; 752 } 753 } 754 755 private synchronized long renewDelegationToken(final Token<?> token 756 ) throws IOException { 757 final HttpOpParam.Op op = PutOpParam.Op.RENEWDELEGATIONTOKEN; 758 TokenArgumentParam dtargParam = new TokenArgumentParam( 759 token.encodeToUrlString()); 760 final Map<?, ?> m = run(op, null, dtargParam); 761 return (Long) m.get("long"); 762 } 763 764 private synchronized void cancelDelegationToken(final Token<?> token 765 ) throws IOException { 766 final HttpOpParam.Op op = PutOpParam.Op.CANCELDELEGATIONTOKEN; 767 TokenArgumentParam dtargParam = new TokenArgumentParam( 768 token.encodeToUrlString()); 769 run(op, null, dtargParam); 770 } 771 772 @Override 773 public BlockLocation[] getFileBlockLocations(final FileStatus status, 774 final long offset, final long length) throws IOException { 775 if (status == null) { 776 return null; 777 } 778 return getFileBlockLocations(status.getPath(), offset, length); 779 } 780 781 @Override 782 public BlockLocation[] getFileBlockLocations(final Path p, 783 final long offset, final long length) throws IOException { 784 statistics.incrementReadOps(1); 785 786 final HttpOpParam.Op op = GetOpParam.Op.GET_BLOCK_LOCATIONS; 787 final Map<?, ?> m = run(op, p, new OffsetParam(offset), 788 new LengthParam(length)); 789 return DFSUtil.locatedBlocks2Locations(JsonUtil.toLocatedBlocks(m)); 790 } 791 792 @Override 793 public ContentSummary getContentSummary(final Path p) throws IOException { 794 statistics.incrementReadOps(1); 795 796 final HttpOpParam.Op op = GetOpParam.Op.GETCONTENTSUMMARY; 797 final Map<?, ?> m = run(op, p); 798 return JsonUtil.toContentSummary(m); 799 } 800 801 @Override 802 public MD5MD5CRC32FileChecksum getFileChecksum(final Path p 803 ) throws IOException { 804 statistics.incrementReadOps(1); 805 806 final HttpOpParam.Op op = GetOpParam.Op.GETFILECHECKSUM; 807 final Map<?, ?> m = run(op, p); 808 return JsonUtil.toMD5MD5CRC32FileChecksum(m); 809 } 810 811 /** Delegation token renewer. */ 812 public static class DtRenewer extends TokenRenewer { 813 @Override 814 public boolean handleKind(Text kind) { 815 return kind.equals(TOKEN_KIND); 816 } 817 818 @Override 819 public boolean isManaged(Token<?> token) throws IOException { 820 return true; 821 } 822 823 private static WebHdfsFileSystem getWebHdfs( 824 final Token<?> token, final Configuration conf) throws IOException { 825 826 final InetSocketAddress nnAddr = SecurityUtil.getTokenServiceAddr(token); 827 final URI uri = DFSUtil.createUri(WebHdfsFileSystem.SCHEME, nnAddr); 828 return (WebHdfsFileSystem)FileSystem.get(uri, conf); 829 } 830 831 @Override 832 public long renew(final Token<?> token, final Configuration conf 833 ) throws IOException, InterruptedException { 834 return getWebHdfs(token, conf).renewDelegationToken(token); 835 } 836 837 @Override 838 public void cancel(final Token<?> token, final Configuration conf 839 ) throws IOException, InterruptedException { 840 getWebHdfs(token, conf).cancelDelegationToken(token); 841 } 842 } 843 844 private static class WebHdfsDelegationTokenSelector 845 extends AbstractDelegationTokenSelector<DelegationTokenIdentifier> { 846 private static final DelegationTokenSelector hdfsTokenSelector = 847 new DelegationTokenSelector(); 848 849 public WebHdfsDelegationTokenSelector() { 850 super(TOKEN_KIND); 851 } 852 853 Token<DelegationTokenIdentifier> selectToken(URI nnUri, 854 Collection<Token<?>> tokens, Configuration conf) { 855 Token<DelegationTokenIdentifier> token = 856 selectToken(SecurityUtil.buildTokenService(nnUri), tokens); 857 if (token == null) { 858 token = hdfsTokenSelector.selectToken(nnUri, tokens, conf); 859 } 860 return token; 861 } 862 } 863 }