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.InetSocketAddress;
023import java.net.URI;
024import java.net.URISyntaxException;
025import java.security.PrivilegedExceptionAction;
026import java.util.EnumSet;
027
028import javax.servlet.ServletContext;
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 org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.apache.hadoop.conf.Configuration;
046import org.apache.hadoop.fs.CreateFlag;
047import org.apache.hadoop.fs.FSDataOutputStream;
048import org.apache.hadoop.fs.MD5MD5CRC32FileChecksum;
049import org.apache.hadoop.fs.permission.FsPermission;
050import org.apache.hadoop.hdfs.DFSClient;
051import org.apache.hadoop.hdfs.DFSClient.DFSDataInputStream;
052import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
053import org.apache.hadoop.hdfs.server.datanode.DataNode;
054import org.apache.hadoop.hdfs.server.namenode.NameNode;
055import org.apache.hadoop.hdfs.web.JsonUtil;
056import org.apache.hadoop.hdfs.web.ParamFilter;
057import org.apache.hadoop.hdfs.web.WebHdfsFileSystem;
058import org.apache.hadoop.hdfs.web.resources.BlockSizeParam;
059import org.apache.hadoop.hdfs.web.resources.BufferSizeParam;
060import org.apache.hadoop.hdfs.web.resources.DelegationParam;
061import org.apache.hadoop.hdfs.web.resources.GetOpParam;
062import org.apache.hadoop.hdfs.web.resources.HttpOpParam;
063import org.apache.hadoop.hdfs.web.resources.LengthParam;
064import org.apache.hadoop.hdfs.web.resources.NamenodeRpcAddressParam;
065import org.apache.hadoop.hdfs.web.resources.OffsetParam;
066import org.apache.hadoop.hdfs.web.resources.OverwriteParam;
067import org.apache.hadoop.hdfs.web.resources.Param;
068import org.apache.hadoop.hdfs.web.resources.PermissionParam;
069import org.apache.hadoop.hdfs.web.resources.PostOpParam;
070import org.apache.hadoop.hdfs.web.resources.PutOpParam;
071import org.apache.hadoop.hdfs.web.resources.ReplicationParam;
072import org.apache.hadoop.hdfs.web.resources.UriFsPathParam;
073import org.apache.hadoop.io.IOUtils;
074import org.apache.hadoop.security.SecurityUtil;
075import org.apache.hadoop.security.UserGroupInformation;
076import org.apache.hadoop.security.token.Token;
077
078import com.sun.jersey.spi.container.ResourceFilters;
079
080/** Web-hdfs DataNode implementation. */
081@Path("")
082@ResourceFilters(ParamFilter.class)
083public class DatanodeWebHdfsMethods {
084  public static final Log LOG = LogFactory.getLog(DatanodeWebHdfsMethods.class);
085
086  private static final UriFsPathParam ROOT = new UriFsPathParam("");
087
088  private @Context ServletContext context;
089  private @Context HttpServletResponse response;
090
091  private void init(final UserGroupInformation ugi,
092      final DelegationParam delegation, final InetSocketAddress nnRpcAddr,
093      final UriFsPathParam path, final HttpOpParam<?> op,
094      final Param<?, ?>... parameters) throws IOException {
095    if (LOG.isTraceEnabled()) {
096      LOG.trace("HTTP " + op.getValue().getType() + ": " + op + ", " + path
097          + ", ugi=" + ugi + Param.toSortedString(", ", parameters));
098    }
099
100    //clear content type
101    response.setContentType(null);
102    
103    if (UserGroupInformation.isSecurityEnabled()) {
104      //add a token for RPC.
105      final Token<DelegationTokenIdentifier> token = 
106          new Token<DelegationTokenIdentifier>();
107      token.decodeFromUrlString(delegation.getValue());
108      SecurityUtil.setTokenService(token, nnRpcAddr);
109      token.setKind(DelegationTokenIdentifier.HDFS_DELEGATION_KIND);
110      ugi.addToken(token);
111    }
112  }
113
114  /** Handle HTTP PUT request for the root. */
115  @PUT
116  @Path("/")
117  @Consumes({"*/*"})
118  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
119  public Response putRoot(
120      final InputStream in,
121      @Context final UserGroupInformation ugi,
122      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
123          final DelegationParam delegation,
124      @QueryParam(NamenodeRpcAddressParam.NAME) 
125      @DefaultValue(NamenodeRpcAddressParam.DEFAULT) 
126          final NamenodeRpcAddressParam namenodeRpcAddress,
127      @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT)
128          final PutOpParam op,
129      @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT)
130          final PermissionParam permission,
131      @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT)
132          final OverwriteParam overwrite,
133      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
134          final BufferSizeParam bufferSize,
135      @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT)
136          final ReplicationParam replication,
137      @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT)
138          final BlockSizeParam blockSize
139      ) throws IOException, InterruptedException {
140    return put(in, ugi, delegation, namenodeRpcAddress, ROOT, op, permission,
141        overwrite, bufferSize, replication, blockSize);
142  }
143
144  /** Handle HTTP PUT request. */
145  @PUT
146  @Path("{" + UriFsPathParam.NAME + ":.*}")
147  @Consumes({"*/*"})
148  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
149  public Response put(
150      final InputStream in,
151      @Context final UserGroupInformation ugi,
152      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
153          final DelegationParam delegation,
154      @QueryParam(NamenodeRpcAddressParam.NAME)
155      @DefaultValue(NamenodeRpcAddressParam.DEFAULT)
156          final NamenodeRpcAddressParam namenodeRpcAddress,
157      @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
158      @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT)
159          final PutOpParam op,
160      @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT)
161          final PermissionParam permission,
162      @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT)
163          final OverwriteParam overwrite,
164      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
165          final BufferSizeParam bufferSize,
166      @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT)
167          final ReplicationParam replication,
168      @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT)
169          final BlockSizeParam blockSize
170      ) throws IOException, InterruptedException {
171
172    final InetSocketAddress nnRpcAddr = namenodeRpcAddress.getValue();
173    init(ugi, delegation, nnRpcAddr, path, op, permission,
174        overwrite, bufferSize, replication, blockSize);
175
176    return ugi.doAs(new PrivilegedExceptionAction<Response>() {
177      @Override
178      public Response run() throws IOException, URISyntaxException {
179
180    final String fullpath = path.getAbsolutePath();
181    final DataNode datanode = (DataNode)context.getAttribute("datanode");
182
183    switch(op.getValue()) {
184    case CREATE:
185    {
186      final Configuration conf = new Configuration(datanode.getConf());
187      conf.set(FsPermission.UMASK_LABEL, "000");
188
189      final int b = bufferSize.getValue(conf);
190      DFSClient dfsclient = new DFSClient(nnRpcAddr, conf);
191      FSDataOutputStream out = null;
192      try {
193        out = new FSDataOutputStream(dfsclient.create(
194            fullpath, permission.getFsPermission(), 
195            overwrite.getValue() ? EnumSet.of(CreateFlag.CREATE, CreateFlag.OVERWRITE)
196                : EnumSet.of(CreateFlag.CREATE),
197            replication.getValue(conf), blockSize.getValue(conf), null, b, null), null);
198        IOUtils.copyBytes(in, out, b);
199        out.close();
200        out = null;
201        dfsclient.close();
202        dfsclient = null;
203      } finally {
204        IOUtils.cleanup(LOG, out);
205        IOUtils.cleanup(LOG, dfsclient);
206      }
207      final InetSocketAddress nnHttpAddr = NameNode.getHttpAddress(conf);
208      final URI uri = new URI(WebHdfsFileSystem.SCHEME, null,
209          nnHttpAddr.getHostName(), nnHttpAddr.getPort(), fullpath, null, null);
210      return Response.created(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
211    }
212    default:
213      throw new UnsupportedOperationException(op + " is not supported");
214    }
215      }
216    });
217  }
218
219  /** Handle HTTP POST request for the root for the root. */
220  @POST
221  @Path("/")
222  @Consumes({"*/*"})
223  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
224  public Response postRoot(
225      final InputStream in,
226      @Context final UserGroupInformation ugi,
227      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
228          final DelegationParam delegation,
229      @QueryParam(NamenodeRpcAddressParam.NAME)
230      @DefaultValue(NamenodeRpcAddressParam.DEFAULT)
231          final NamenodeRpcAddressParam namenodeRpcAddress,
232      @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT)
233          final PostOpParam op,
234      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
235          final BufferSizeParam bufferSize
236      ) throws IOException, InterruptedException {
237    return post(in, ugi, delegation, namenodeRpcAddress, ROOT, op, bufferSize);
238  }
239
240  /** Handle HTTP POST request. */
241  @POST
242  @Path("{" + UriFsPathParam.NAME + ":.*}")
243  @Consumes({"*/*"})
244  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
245  public Response post(
246      final InputStream in,
247      @Context final UserGroupInformation ugi,
248      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
249          final DelegationParam delegation,
250      @QueryParam(NamenodeRpcAddressParam.NAME)
251      @DefaultValue(NamenodeRpcAddressParam.DEFAULT)
252          final NamenodeRpcAddressParam namenodeRpcAddress,
253      @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
254      @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT)
255          final PostOpParam op,
256      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
257          final BufferSizeParam bufferSize
258      ) throws IOException, InterruptedException {
259
260    final InetSocketAddress nnRpcAddr = namenodeRpcAddress.getValue();
261    init(ugi, delegation, nnRpcAddr, path, op, bufferSize);
262
263    return ugi.doAs(new PrivilegedExceptionAction<Response>() {
264      @Override
265      public Response run() throws IOException {
266
267    final String fullpath = path.getAbsolutePath();
268    final DataNode datanode = (DataNode)context.getAttribute("datanode");
269
270    switch(op.getValue()) {
271    case APPEND:
272    {
273      final Configuration conf = new Configuration(datanode.getConf());
274      final int b = bufferSize.getValue(conf);
275      DFSClient dfsclient = new DFSClient(nnRpcAddr, conf);
276      FSDataOutputStream out = null;
277      try {
278        out = dfsclient.append(fullpath, b, null, null);
279        IOUtils.copyBytes(in, out, b);
280        out.close();
281        out = null;
282        dfsclient.close();
283        dfsclient = null;
284      } finally {
285        IOUtils.cleanup(LOG, out);
286        IOUtils.cleanup(LOG, dfsclient);
287      }
288      return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
289    }
290    default:
291      throw new UnsupportedOperationException(op + " is not supported");
292    }
293      }
294    });
295  }
296
297  /** Handle HTTP GET request for the root. */
298  @GET
299  @Path("/")
300  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
301  public Response getRoot(
302      @Context final UserGroupInformation ugi,
303      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
304          final DelegationParam delegation,
305      @QueryParam(NamenodeRpcAddressParam.NAME)
306      @DefaultValue(NamenodeRpcAddressParam.DEFAULT)
307          final NamenodeRpcAddressParam namenodeRpcAddress,
308      @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT)
309          final GetOpParam op,
310      @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT)
311          final OffsetParam offset,
312      @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT)
313          final LengthParam length,
314      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
315          final BufferSizeParam bufferSize
316      ) throws IOException, InterruptedException {
317    return get(ugi, delegation, namenodeRpcAddress, ROOT, op, offset, length,
318        bufferSize);
319  }
320
321  /** Handle HTTP GET request. */
322  @GET
323  @Path("{" + UriFsPathParam.NAME + ":.*}")
324  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
325  public Response get(
326      @Context final UserGroupInformation ugi,
327      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
328          final DelegationParam delegation,
329      @QueryParam(NamenodeRpcAddressParam.NAME)
330      @DefaultValue(NamenodeRpcAddressParam.DEFAULT)
331          final NamenodeRpcAddressParam namenodeRpcAddress,
332      @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
333      @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT)
334          final GetOpParam op,
335      @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT)
336          final OffsetParam offset,
337      @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT)
338          final LengthParam length,
339      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
340          final BufferSizeParam bufferSize
341      ) throws IOException, InterruptedException {
342
343    final InetSocketAddress nnRpcAddr = namenodeRpcAddress.getValue();
344    init(ugi, delegation, nnRpcAddr, path, op, offset, length, bufferSize);
345
346    return ugi.doAs(new PrivilegedExceptionAction<Response>() {
347      @Override
348      public Response run() throws IOException {
349
350    final String fullpath = path.getAbsolutePath();
351    final DataNode datanode = (DataNode)context.getAttribute("datanode");
352    final Configuration conf = new Configuration(datanode.getConf());
353
354    switch(op.getValue()) {
355    case OPEN:
356    {
357      final int b = bufferSize.getValue(conf);
358      final DFSClient dfsclient = new DFSClient(nnRpcAddr, conf);
359      DFSDataInputStream in = null;
360      try {
361        in = new DFSClient.DFSDataInputStream(
362            dfsclient.open(fullpath, b, true));
363        in.seek(offset.getValue());
364      } catch(IOException ioe) {
365        IOUtils.cleanup(LOG, in);
366        IOUtils.cleanup(LOG, dfsclient);
367        throw ioe;
368      }
369      
370      final long n = length.getValue() != null? length.getValue()
371        : in.getVisibleLength() - offset.getValue();
372      return Response.ok(new OpenEntity(in, n, dfsclient)).type(
373          MediaType.APPLICATION_OCTET_STREAM).build();
374    }
375    case GETFILECHECKSUM:
376    {
377      MD5MD5CRC32FileChecksum checksum = null;
378      DFSClient dfsclient = new DFSClient(nnRpcAddr, conf);
379      try {
380        checksum = dfsclient.getFileChecksum(fullpath);
381        dfsclient.close();
382        dfsclient = null;
383      } finally {
384        IOUtils.cleanup(LOG, dfsclient);
385      }
386      final String js = JsonUtil.toJsonString(checksum);
387      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
388    }
389    default:
390      throw new UnsupportedOperationException(op + " is not supported");
391    }
392      }
393    });
394  }
395}