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 */ 018package org.apache.hadoop.hdfs.server.datanode.web.resources; 019 020import java.io.IOException; 021import java.io.InputStream; 022import java.net.URI; 023import java.net.URISyntaxException; 024import java.security.PrivilegedExceptionAction; 025import java.util.EnumSet; 026 027import javax.servlet.ServletContext; 028import javax.servlet.http.HttpServletRequest; 029import javax.servlet.http.HttpServletResponse; 030import javax.ws.rs.Consumes; 031import javax.ws.rs.DefaultValue; 032import javax.ws.rs.GET; 033import javax.ws.rs.POST; 034import javax.ws.rs.PUT; 035import javax.ws.rs.Path; 036import javax.ws.rs.PathParam; 037import javax.ws.rs.Produces; 038import javax.ws.rs.QueryParam; 039import javax.ws.rs.core.Context; 040import javax.ws.rs.core.MediaType; 041import javax.ws.rs.core.Response; 042 043import com.google.common.annotations.VisibleForTesting; 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046import org.apache.hadoop.conf.Configuration; 047import org.apache.hadoop.fs.CreateFlag; 048import org.apache.hadoop.fs.FSDataOutputStream; 049import org.apache.hadoop.fs.MD5MD5CRC32FileChecksum; 050import org.apache.hadoop.fs.permission.FsPermission; 051import org.apache.hadoop.hdfs.DFSClient; 052import org.apache.hadoop.hdfs.HAUtil; 053import org.apache.hadoop.hdfs.client.HdfsDataInputStream; 054import org.apache.hadoop.hdfs.protocol.HdfsConstants; 055import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; 056import org.apache.hadoop.hdfs.server.datanode.DataNode; 057import org.apache.hadoop.hdfs.web.JsonUtil; 058import org.apache.hadoop.hdfs.web.ParamFilter; 059import org.apache.hadoop.hdfs.web.SWebHdfsFileSystem; 060import org.apache.hadoop.hdfs.web.WebHdfsFileSystem; 061import org.apache.hadoop.hdfs.web.resources.BlockSizeParam; 062import org.apache.hadoop.hdfs.web.resources.BufferSizeParam; 063import org.apache.hadoop.hdfs.web.resources.DelegationParam; 064import org.apache.hadoop.hdfs.web.resources.GetOpParam; 065import org.apache.hadoop.hdfs.web.resources.HttpOpParam; 066import org.apache.hadoop.hdfs.web.resources.LengthParam; 067import org.apache.hadoop.hdfs.web.resources.NamenodeAddressParam; 068import org.apache.hadoop.hdfs.web.resources.OffsetParam; 069import org.apache.hadoop.hdfs.web.resources.OverwriteParam; 070import org.apache.hadoop.hdfs.web.resources.Param; 071import org.apache.hadoop.hdfs.web.resources.PermissionParam; 072import org.apache.hadoop.hdfs.web.resources.PostOpParam; 073import org.apache.hadoop.hdfs.web.resources.PutOpParam; 074import org.apache.hadoop.hdfs.web.resources.ReplicationParam; 075import org.apache.hadoop.hdfs.web.resources.UriFsPathParam; 076import org.apache.hadoop.io.IOUtils; 077import org.apache.hadoop.security.SecurityUtil; 078import org.apache.hadoop.security.UserGroupInformation; 079import org.apache.hadoop.security.token.Token; 080 081import com.sun.jersey.spi.container.ResourceFilters; 082 083/** Web-hdfs DataNode implementation. */ 084@Path("") 085@ResourceFilters(ParamFilter.class) 086public class DatanodeWebHdfsMethods { 087 public static final Log LOG = LogFactory.getLog(DatanodeWebHdfsMethods.class); 088 089 private static final UriFsPathParam ROOT = new UriFsPathParam(""); 090 091 private @Context ServletContext context; 092 private @Context HttpServletRequest request; 093 private @Context HttpServletResponse response; 094 095 private void init(final UserGroupInformation ugi, 096 final DelegationParam delegation, final String nnId, 097 final UriFsPathParam path, final HttpOpParam<?> op, 098 final Param<?, ?>... parameters) throws IOException { 099 if (LOG.isTraceEnabled()) { 100 LOG.trace("HTTP " + op.getValue().getType() + ": " + op + ", " + path 101 + ", ugi=" + ugi + Param.toSortedString(", ", parameters)); 102 } 103 if (nnId == null) { 104 throw new IllegalArgumentException(NamenodeAddressParam.NAME 105 + " is not specified."); 106 } 107 108 //clear content type 109 response.setContentType(null); 110 111 if (UserGroupInformation.isSecurityEnabled()) { 112 //add a token for RPC. 113 final Token<DelegationTokenIdentifier> token = deserializeToken 114 (delegation.getValue(), nnId); 115 ugi.addToken(token); 116 } 117 } 118 119 @VisibleForTesting 120 Token<DelegationTokenIdentifier> deserializeToken 121 (String delegation,String nnId) throws IOException { 122 final DataNode datanode = (DataNode) context.getAttribute("datanode"); 123 final Configuration conf = datanode.getConf(); 124 final Token<DelegationTokenIdentifier> token = new 125 Token<DelegationTokenIdentifier>(); 126 token.decodeFromUrlString(delegation); 127 URI nnUri = URI.create(HdfsConstants.HDFS_URI_SCHEME + 128 "://" + nnId); 129 boolean isLogical = HAUtil.isLogicalUri(conf, nnUri); 130 if (isLogical) { 131 token.setService(HAUtil.buildTokenServiceForLogicalUri(nnUri)); 132 } else { 133 token.setService(SecurityUtil.buildTokenService(nnUri)); 134 } 135 token.setKind(DelegationTokenIdentifier.HDFS_DELEGATION_KIND); 136 return token; 137 } 138 139 /** Handle HTTP PUT request for the root. */ 140 @PUT 141 @Path("/") 142 @Consumes({"*/*"}) 143 @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON}) 144 public Response putRoot( 145 final InputStream in, 146 @Context final UserGroupInformation ugi, 147 @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT) 148 final DelegationParam delegation, 149 @QueryParam(NamenodeAddressParam.NAME) 150 @DefaultValue(NamenodeAddressParam.DEFAULT) 151 final NamenodeAddressParam namenode, 152 @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT) 153 final PutOpParam op, 154 @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT) 155 final PermissionParam permission, 156 @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT) 157 final OverwriteParam overwrite, 158 @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT) 159 final BufferSizeParam bufferSize, 160 @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT) 161 final ReplicationParam replication, 162 @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT) 163 final BlockSizeParam blockSize 164 ) throws IOException, InterruptedException { 165 return put(in, ugi, delegation, namenode, ROOT, op, permission, 166 overwrite, bufferSize, replication, blockSize); 167 } 168 169 /** Handle HTTP PUT request. */ 170 @PUT 171 @Path("{" + UriFsPathParam.NAME + ":.*}") 172 @Consumes({"*/*"}) 173 @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON}) 174 public Response put( 175 final InputStream in, 176 @Context final UserGroupInformation ugi, 177 @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT) 178 final DelegationParam delegation, 179 @QueryParam(NamenodeAddressParam.NAME) 180 @DefaultValue(NamenodeAddressParam.DEFAULT) 181 final NamenodeAddressParam namenode, 182 @PathParam(UriFsPathParam.NAME) final UriFsPathParam path, 183 @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT) 184 final PutOpParam op, 185 @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT) 186 final PermissionParam permission, 187 @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT) 188 final OverwriteParam overwrite, 189 @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT) 190 final BufferSizeParam bufferSize, 191 @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT) 192 final ReplicationParam replication, 193 @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT) 194 final BlockSizeParam blockSize 195 ) throws IOException, InterruptedException { 196 197 final String nnId = namenode.getValue(); 198 init(ugi, delegation, nnId, path, op, permission, 199 overwrite, bufferSize, replication, blockSize); 200 201 return ugi.doAs(new PrivilegedExceptionAction<Response>() { 202 @Override 203 public Response run() throws IOException, URISyntaxException { 204 return put(in, nnId, path.getAbsolutePath(), op, 205 permission, overwrite, bufferSize, replication, blockSize); 206 } 207 }); 208 } 209 210 private Response put( 211 final InputStream in, 212 final String nnId, 213 final String fullpath, 214 final PutOpParam op, 215 final PermissionParam permission, 216 final OverwriteParam overwrite, 217 final BufferSizeParam bufferSize, 218 final ReplicationParam replication, 219 final BlockSizeParam blockSize 220 ) throws IOException, URISyntaxException { 221 final DataNode datanode = (DataNode)context.getAttribute("datanode"); 222 223 switch(op.getValue()) { 224 case CREATE: 225 { 226 final Configuration conf = new Configuration(datanode.getConf()); 227 conf.set(FsPermission.UMASK_LABEL, "000"); 228 229 final int b = bufferSize.getValue(conf); 230 DFSClient dfsclient = newDfsClient(nnId, conf); 231 FSDataOutputStream out = null; 232 try { 233 out = new FSDataOutputStream(dfsclient.create( 234 fullpath, permission.getFsPermission(), 235 overwrite.getValue() ? EnumSet.of(CreateFlag.CREATE, CreateFlag.OVERWRITE) 236 : EnumSet.of(CreateFlag.CREATE), 237 replication.getValue(conf), blockSize.getValue(conf), null, b, null), null); 238 IOUtils.copyBytes(in, out, b); 239 out.close(); 240 out = null; 241 dfsclient.close(); 242 dfsclient = null; 243 } finally { 244 IOUtils.cleanup(LOG, out); 245 IOUtils.cleanup(LOG, dfsclient); 246 } 247 final String scheme = "http".equals(request.getScheme()) ? 248 WebHdfsFileSystem.SCHEME : SWebHdfsFileSystem.SCHEME; 249 final URI uri = new URI(scheme, nnId, fullpath, null, null); 250 return Response.created(uri).type(MediaType.APPLICATION_OCTET_STREAM).build(); 251 } 252 default: 253 throw new UnsupportedOperationException(op + " is not supported"); 254 } 255 } 256 257 /** Handle HTTP POST request for the root for the root. */ 258 @POST 259 @Path("/") 260 @Consumes({"*/*"}) 261 @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON}) 262 public Response postRoot( 263 final InputStream in, 264 @Context final UserGroupInformation ugi, 265 @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT) 266 final DelegationParam delegation, 267 @QueryParam(NamenodeAddressParam.NAME) 268 @DefaultValue(NamenodeAddressParam.DEFAULT) 269 final NamenodeAddressParam namenode, 270 @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT) 271 final PostOpParam op, 272 @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT) 273 final BufferSizeParam bufferSize 274 ) throws IOException, InterruptedException { 275 return post(in, ugi, delegation, namenode, ROOT, op, bufferSize); 276 } 277 278 /** Handle HTTP POST request. */ 279 @POST 280 @Path("{" + UriFsPathParam.NAME + ":.*}") 281 @Consumes({"*/*"}) 282 @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON}) 283 public Response post( 284 final InputStream in, 285 @Context final UserGroupInformation ugi, 286 @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT) 287 final DelegationParam delegation, 288 @QueryParam(NamenodeAddressParam.NAME) 289 @DefaultValue(NamenodeAddressParam.DEFAULT) 290 final NamenodeAddressParam namenode, 291 @PathParam(UriFsPathParam.NAME) final UriFsPathParam path, 292 @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT) 293 final PostOpParam op, 294 @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT) 295 final BufferSizeParam bufferSize 296 ) throws IOException, InterruptedException { 297 298 final String nnId = namenode.getValue(); 299 init(ugi, delegation, nnId, path, op, bufferSize); 300 301 return ugi.doAs(new PrivilegedExceptionAction<Response>() { 302 @Override 303 public Response run() throws IOException { 304 return post(in, nnId, path.getAbsolutePath(), op, 305 bufferSize); 306 } 307 }); 308 } 309 310 private Response post( 311 final InputStream in, 312 final String nnId, 313 final String fullpath, 314 final PostOpParam op, 315 final BufferSizeParam bufferSize 316 ) throws IOException { 317 final DataNode datanode = (DataNode)context.getAttribute("datanode"); 318 319 switch(op.getValue()) { 320 case APPEND: 321 { 322 final Configuration conf = new Configuration(datanode.getConf()); 323 final int b = bufferSize.getValue(conf); 324 DFSClient dfsclient = newDfsClient(nnId, conf); 325 FSDataOutputStream out = null; 326 try { 327 out = dfsclient.append(fullpath, b, null, null); 328 IOUtils.copyBytes(in, out, b); 329 out.close(); 330 out = null; 331 dfsclient.close(); 332 dfsclient = null; 333 } finally { 334 IOUtils.cleanup(LOG, out); 335 IOUtils.cleanup(LOG, dfsclient); 336 } 337 return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build(); 338 } 339 default: 340 throw new UnsupportedOperationException(op + " is not supported"); 341 } 342 } 343 344 /** Handle HTTP GET request for the root. */ 345 @GET 346 @Path("/") 347 @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON}) 348 public Response getRoot( 349 @Context final UserGroupInformation ugi, 350 @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT) 351 final DelegationParam delegation, 352 @QueryParam(NamenodeAddressParam.NAME) 353 @DefaultValue(NamenodeAddressParam.DEFAULT) 354 final NamenodeAddressParam namenode, 355 @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT) 356 final GetOpParam op, 357 @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT) 358 final OffsetParam offset, 359 @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT) 360 final LengthParam length, 361 @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT) 362 final BufferSizeParam bufferSize 363 ) throws IOException, InterruptedException { 364 return get(ugi, delegation, namenode, ROOT, op, offset, length, 365 bufferSize); 366 } 367 368 /** Handle HTTP GET request. */ 369 @GET 370 @Path("{" + UriFsPathParam.NAME + ":.*}") 371 @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON}) 372 public Response get( 373 @Context final UserGroupInformation ugi, 374 @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT) 375 final DelegationParam delegation, 376 @QueryParam(NamenodeAddressParam.NAME) 377 @DefaultValue(NamenodeAddressParam.DEFAULT) 378 final NamenodeAddressParam namenode, 379 @PathParam(UriFsPathParam.NAME) final UriFsPathParam path, 380 @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT) 381 final GetOpParam op, 382 @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT) 383 final OffsetParam offset, 384 @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT) 385 final LengthParam length, 386 @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT) 387 final BufferSizeParam bufferSize 388 ) throws IOException, InterruptedException { 389 390 final String nnId = namenode.getValue(); 391 init(ugi, delegation, nnId, path, op, offset, length, bufferSize); 392 393 return ugi.doAs(new PrivilegedExceptionAction<Response>() { 394 @Override 395 public Response run() throws IOException { 396 return get(nnId, path.getAbsolutePath(), op, offset, 397 length, bufferSize); 398 } 399 }); 400 } 401 402 private Response get( 403 final String nnId, 404 final String fullpath, 405 final GetOpParam op, 406 final OffsetParam offset, 407 final LengthParam length, 408 final BufferSizeParam bufferSize 409 ) throws IOException { 410 final DataNode datanode = (DataNode)context.getAttribute("datanode"); 411 final Configuration conf = new Configuration(datanode.getConf()); 412 413 switch(op.getValue()) { 414 case OPEN: 415 { 416 final int b = bufferSize.getValue(conf); 417 final DFSClient dfsclient = newDfsClient(nnId, conf); 418 HdfsDataInputStream in = null; 419 try { 420 in = new HdfsDataInputStream(dfsclient.open(fullpath, b, true)); 421 in.seek(offset.getValue()); 422 } catch(IOException ioe) { 423 IOUtils.cleanup(LOG, in); 424 IOUtils.cleanup(LOG, dfsclient); 425 throw ioe; 426 } 427 428 final long n = length.getValue() != null ? 429 Math.min(length.getValue(), in.getVisibleLength() - offset.getValue()) : 430 in.getVisibleLength() - offset.getValue(); 431 432 // jetty 6 reserves 12 bytes in the out buffer for chunked responses 433 // (file length > 2GB) which causes extremely poor performance when 434 // 12 bytes of the output spill into another buffer which results 435 // in a big and little write 436 int outBufferSize = response.getBufferSize(); 437 if (n > Integer.MAX_VALUE) { 438 outBufferSize -= 12; 439 } 440 /** 441 * Allow the Web UI to perform an AJAX request to get the data. 442 */ 443 return Response.ok(new OpenEntity(in, n, outBufferSize, dfsclient)) 444 .type(MediaType.APPLICATION_OCTET_STREAM) 445 .header("Access-Control-Allow-Methods", "GET") 446 .header("Access-Control-Allow-Origin", "*") 447 .build(); 448 } 449 case GETFILECHECKSUM: 450 { 451 MD5MD5CRC32FileChecksum checksum = null; 452 DFSClient dfsclient = newDfsClient(nnId, conf); 453 try { 454 checksum = dfsclient.getFileChecksum(fullpath, Long.MAX_VALUE); 455 dfsclient.close(); 456 dfsclient = null; 457 } finally { 458 IOUtils.cleanup(LOG, dfsclient); 459 } 460 final String js = JsonUtil.toJsonString(checksum); 461 return Response.ok(js).type(MediaType.APPLICATION_JSON).build(); 462 } 463 default: 464 throw new UnsupportedOperationException(op + " is not supported"); 465 } 466 } 467 468 private static DFSClient newDfsClient(String nnId, 469 Configuration conf) throws IOException { 470 URI uri = URI.create(HdfsConstants.HDFS_URI_SCHEME + "://" + nnId); 471 return new DFSClient(uri, conf); 472 } 473}