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