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    package org.apache.hadoop.hdfs.server.namenode.web.resources;
019    
020    import java.io.FileNotFoundException;
021    import java.io.IOException;
022    import java.io.OutputStream;
023    import java.io.OutputStreamWriter;
024    import java.io.PrintWriter;
025    import java.net.InetAddress;
026    import java.net.URI;
027    import java.net.URISyntaxException;
028    import java.security.PrivilegedExceptionAction;
029    import java.util.ArrayList;
030    import java.util.EnumSet;
031    
032    import javax.servlet.ServletContext;
033    import javax.servlet.http.HttpServletRequest;
034    import javax.servlet.http.HttpServletResponse;
035    import javax.ws.rs.Consumes;
036    import javax.ws.rs.DELETE;
037    import javax.ws.rs.DefaultValue;
038    import javax.ws.rs.GET;
039    import javax.ws.rs.POST;
040    import javax.ws.rs.PUT;
041    import javax.ws.rs.Path;
042    import javax.ws.rs.PathParam;
043    import javax.ws.rs.Produces;
044    import javax.ws.rs.QueryParam;
045    import javax.ws.rs.core.Context;
046    import javax.ws.rs.core.MediaType;
047    import javax.ws.rs.core.Response;
048    import javax.ws.rs.core.StreamingOutput;
049    
050    import org.apache.commons.logging.Log;
051    import org.apache.commons.logging.LogFactory;
052    import org.apache.hadoop.conf.Configuration;
053    import org.apache.hadoop.fs.ContentSummary;
054    import org.apache.hadoop.fs.FileStatus;
055    import org.apache.hadoop.fs.Options;
056    import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
057    import org.apache.hadoop.hdfs.protocol.DirectoryListing;
058    import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
059    import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
060    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
061    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSecretManager;
062    import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager;
063    import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
064    import org.apache.hadoop.hdfs.server.common.JspHelper;
065    import org.apache.hadoop.hdfs.server.namenode.NameNode;
066    import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocols;
067    import org.apache.hadoop.hdfs.web.JsonUtil;
068    import org.apache.hadoop.hdfs.web.ParamFilter;
069    import org.apache.hadoop.hdfs.web.WebHdfsFileSystem;
070    import org.apache.hadoop.hdfs.web.resources.AccessTimeParam;
071    import org.apache.hadoop.hdfs.web.resources.BlockSizeParam;
072    import org.apache.hadoop.hdfs.web.resources.BufferSizeParam;
073    import org.apache.hadoop.hdfs.web.resources.ConcatSourcesParam;
074    import org.apache.hadoop.hdfs.web.resources.CreateParentParam;
075    import org.apache.hadoop.hdfs.web.resources.DelegationParam;
076    import org.apache.hadoop.hdfs.web.resources.DeleteOpParam;
077    import org.apache.hadoop.hdfs.web.resources.DestinationParam;
078    import org.apache.hadoop.hdfs.web.resources.DoAsParam;
079    import org.apache.hadoop.hdfs.web.resources.GetOpParam;
080    import org.apache.hadoop.hdfs.web.resources.GroupParam;
081    import org.apache.hadoop.hdfs.web.resources.HttpOpParam;
082    import org.apache.hadoop.hdfs.web.resources.LengthParam;
083    import org.apache.hadoop.hdfs.web.resources.ModificationTimeParam;
084    import org.apache.hadoop.hdfs.web.resources.NamenodeRpcAddressParam;
085    import org.apache.hadoop.hdfs.web.resources.OffsetParam;
086    import org.apache.hadoop.hdfs.web.resources.OverwriteParam;
087    import org.apache.hadoop.hdfs.web.resources.OwnerParam;
088    import org.apache.hadoop.hdfs.web.resources.Param;
089    import org.apache.hadoop.hdfs.web.resources.PermissionParam;
090    import org.apache.hadoop.hdfs.web.resources.PostOpParam;
091    import org.apache.hadoop.hdfs.web.resources.PutOpParam;
092    import org.apache.hadoop.hdfs.web.resources.RecursiveParam;
093    import org.apache.hadoop.hdfs.web.resources.RenameOptionSetParam;
094    import org.apache.hadoop.hdfs.web.resources.RenewerParam;
095    import org.apache.hadoop.hdfs.web.resources.ReplicationParam;
096    import org.apache.hadoop.hdfs.web.resources.TokenArgumentParam;
097    import org.apache.hadoop.hdfs.web.resources.UriFsPathParam;
098    import org.apache.hadoop.hdfs.web.resources.UserParam;
099    import org.apache.hadoop.ipc.Server;
100    import org.apache.hadoop.net.NodeBase;
101    import org.apache.hadoop.security.Credentials;
102    import org.apache.hadoop.security.UserGroupInformation;
103    import org.apache.hadoop.security.token.Token;
104    import org.apache.hadoop.security.token.TokenIdentifier;
105    
106    import com.google.common.base.Charsets;
107    import com.sun.jersey.spi.container.ResourceFilters;
108    
109    /** Web-hdfs NameNode implementation. */
110    @Path("")
111    @ResourceFilters(ParamFilter.class)
112    public class NamenodeWebHdfsMethods {
113      public static final Log LOG = LogFactory.getLog(NamenodeWebHdfsMethods.class);
114    
115      private static final UriFsPathParam ROOT = new UriFsPathParam("");
116      
117      private static final ThreadLocal<String> REMOTE_ADDRESS = new ThreadLocal<String>(); 
118    
119      /** @return the remote client address. */
120      public static String getRemoteAddress() {
121        return REMOTE_ADDRESS.get();
122      }
123    
124      public static InetAddress getRemoteIp() {
125        try {
126          return InetAddress.getByName(getRemoteAddress());
127        } catch (Exception e) {
128          return null;
129        }
130      }
131    
132      /**
133       * Returns true if a WebHdfs request is in progress.  Akin to
134       * {@link Server#isRpcInvocation()}.
135       */
136      public static boolean isWebHdfsInvocation() {
137        return getRemoteAddress() != null;
138      }
139    
140      private @Context ServletContext context;
141      private @Context HttpServletRequest request;
142      private @Context HttpServletResponse response;
143    
144      private void init(final UserGroupInformation ugi,
145          final DelegationParam delegation,
146          final UserParam username, final DoAsParam doAsUser,
147          final UriFsPathParam path, final HttpOpParam<?> op,
148          final Param<?, ?>... parameters) {
149        if (LOG.isTraceEnabled()) {
150          LOG.trace("HTTP " + op.getValue().getType() + ": " + op + ", " + path
151              + ", ugi=" + ugi + ", " + username + ", " + doAsUser
152              + Param.toSortedString(", ", parameters));
153        }
154    
155        //clear content type
156        response.setContentType(null);
157      }
158    
159      static DatanodeInfo chooseDatanode(final NameNode namenode,
160          final String path, final HttpOpParam.Op op, final long openOffset,
161          final long blocksize, Configuration conf) throws IOException {
162        final BlockManager bm = namenode.getNamesystem().getBlockManager();
163    
164        if (op == PutOpParam.Op.CREATE) {
165          //choose a datanode near to client 
166          final DatanodeDescriptor clientNode = bm.getDatanodeManager(
167              ).getDatanodeByHost(getRemoteAddress());
168          if (clientNode != null) {
169            final DatanodeDescriptor[] datanodes = bm.getBlockPlacementPolicy()
170                .chooseTarget(path, 1, clientNode,
171                    new ArrayList<DatanodeDescriptor>(), false, null, blocksize);
172            if (datanodes.length > 0) {
173              return datanodes[0];
174            }
175          }
176        } else if (op == GetOpParam.Op.OPEN
177            || op == GetOpParam.Op.GETFILECHECKSUM
178            || op == PostOpParam.Op.APPEND) {
179          //choose a datanode containing a replica 
180          final NamenodeProtocols np = namenode.getRpcServer();
181          final HdfsFileStatus status = np.getFileInfo(path);
182          if (status == null) {
183            throw new FileNotFoundException("File " + path + " not found.");
184          }
185          final long len = status.getLen();
186          if (op == GetOpParam.Op.OPEN) {
187            if (openOffset < 0L || (openOffset >= len && len > 0)) {
188              throw new IOException("Offset=" + openOffset
189                  + " out of the range [0, " + len + "); " + op + ", path=" + path);
190            }
191          }
192    
193          if (len > 0) {
194            final long offset = op == GetOpParam.Op.OPEN? openOffset: len - 1;
195            final LocatedBlocks locations = np.getBlockLocations(path, offset, 1);
196            final int count = locations.locatedBlockCount();
197            if (count > 0) {
198              return JspHelper.bestNode(locations.get(0).getLocations(), false, conf);
199            }
200          }
201        } 
202    
203        return (DatanodeDescriptor)bm.getDatanodeManager().getNetworkTopology(
204            ).chooseRandom(NodeBase.ROOT);
205      }
206    
207      private Token<? extends TokenIdentifier> generateDelegationToken(
208          final NameNode namenode, final UserGroupInformation ugi,
209          final String renewer) throws IOException {
210        final Credentials c = DelegationTokenSecretManager.createCredentials(
211            namenode, ugi, renewer != null? renewer: ugi.getShortUserName());
212        final Token<? extends TokenIdentifier> t = c.getAllTokens().iterator().next();
213        t.setKind(WebHdfsFileSystem.TOKEN_KIND);
214        return t;
215      }
216    
217      private URI redirectURI(final NameNode namenode,
218          final UserGroupInformation ugi, final DelegationParam delegation,
219          final UserParam username, final DoAsParam doAsUser,
220          final String path, final HttpOpParam.Op op, final long openOffset,
221          final long blocksize,
222          final Param<?, ?>... parameters) throws URISyntaxException, IOException {
223        final Configuration conf = (Configuration)context.getAttribute(JspHelper.CURRENT_CONF);
224        final DatanodeInfo dn = chooseDatanode(namenode, path, op, openOffset,
225            blocksize, conf);
226    
227        final String delegationQuery;
228        if (!UserGroupInformation.isSecurityEnabled()) {
229          //security disabled
230          delegationQuery = Param.toSortedString("&", doAsUser, username);
231        } else if (delegation.getValue() != null) {
232          //client has provided a token
233          delegationQuery = "&" + delegation;
234        } else {
235          //generate a token
236          final Token<? extends TokenIdentifier> t = generateDelegationToken(
237              namenode, ugi, request.getUserPrincipal().getName());
238          delegationQuery = "&" + new DelegationParam(t.encodeToUrlString());
239        }
240        final String query = op.toQueryString() + delegationQuery
241            + "&" + new NamenodeRpcAddressParam(namenode)
242            + Param.toSortedString("&", parameters);
243        final String uripath = WebHdfsFileSystem.PATH_PREFIX + path;
244    
245        final URI uri = new URI("http", null, dn.getHostName(), dn.getInfoPort(),
246            uripath, query, null);
247        if (LOG.isTraceEnabled()) {
248          LOG.trace("redirectURI=" + uri);
249        }
250        return uri;
251      }
252    
253      /** Handle HTTP PUT request for the root. */
254      @PUT
255      @Path("/")
256      @Consumes({"*/*"})
257      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
258      public Response putRoot(
259          @Context final UserGroupInformation ugi,
260          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
261              final DelegationParam delegation,
262          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
263              final UserParam username,
264          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
265              final DoAsParam doAsUser,
266          @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT)
267              final PutOpParam op,
268          @QueryParam(DestinationParam.NAME) @DefaultValue(DestinationParam.DEFAULT)
269              final DestinationParam destination,
270          @QueryParam(OwnerParam.NAME) @DefaultValue(OwnerParam.DEFAULT)
271              final OwnerParam owner,
272          @QueryParam(GroupParam.NAME) @DefaultValue(GroupParam.DEFAULT)
273              final GroupParam group,
274          @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT)
275              final PermissionParam permission,
276          @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT)
277              final OverwriteParam overwrite,
278          @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
279              final BufferSizeParam bufferSize,
280          @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT)
281              final ReplicationParam replication,
282          @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT)
283              final BlockSizeParam blockSize,
284          @QueryParam(ModificationTimeParam.NAME) @DefaultValue(ModificationTimeParam.DEFAULT)
285              final ModificationTimeParam modificationTime,
286          @QueryParam(AccessTimeParam.NAME) @DefaultValue(AccessTimeParam.DEFAULT)
287              final AccessTimeParam accessTime,
288          @QueryParam(RenameOptionSetParam.NAME) @DefaultValue(RenameOptionSetParam.DEFAULT)
289              final RenameOptionSetParam renameOptions,
290          @QueryParam(CreateParentParam.NAME) @DefaultValue(CreateParentParam.DEFAULT)
291              final CreateParentParam createParent,
292          @QueryParam(TokenArgumentParam.NAME) @DefaultValue(TokenArgumentParam.DEFAULT)
293              final TokenArgumentParam delegationTokenArgument
294          ) throws IOException, InterruptedException {
295        return put(ugi, delegation, username, doAsUser, ROOT, op, destination,
296            owner, group, permission, overwrite, bufferSize, replication,
297            blockSize, modificationTime, accessTime, renameOptions, createParent,
298            delegationTokenArgument);
299      }
300    
301      /** Handle HTTP PUT request. */
302      @PUT
303      @Path("{" + UriFsPathParam.NAME + ":.*}")
304      @Consumes({"*/*"})
305      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
306      public Response put(
307          @Context final UserGroupInformation ugi,
308          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
309              final DelegationParam delegation,
310          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
311              final UserParam username,
312          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
313              final DoAsParam doAsUser,
314          @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
315          @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT)
316              final PutOpParam op,
317          @QueryParam(DestinationParam.NAME) @DefaultValue(DestinationParam.DEFAULT)
318              final DestinationParam destination,
319          @QueryParam(OwnerParam.NAME) @DefaultValue(OwnerParam.DEFAULT)
320              final OwnerParam owner,
321          @QueryParam(GroupParam.NAME) @DefaultValue(GroupParam.DEFAULT)
322              final GroupParam group,
323          @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT)
324              final PermissionParam permission,
325          @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT)
326              final OverwriteParam overwrite,
327          @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
328              final BufferSizeParam bufferSize,
329          @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT)
330              final ReplicationParam replication,
331          @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT)
332              final BlockSizeParam blockSize,
333          @QueryParam(ModificationTimeParam.NAME) @DefaultValue(ModificationTimeParam.DEFAULT)
334              final ModificationTimeParam modificationTime,
335          @QueryParam(AccessTimeParam.NAME) @DefaultValue(AccessTimeParam.DEFAULT)
336              final AccessTimeParam accessTime,
337          @QueryParam(RenameOptionSetParam.NAME) @DefaultValue(RenameOptionSetParam.DEFAULT)
338              final RenameOptionSetParam renameOptions,
339          @QueryParam(CreateParentParam.NAME) @DefaultValue(CreateParentParam.DEFAULT)
340              final CreateParentParam createParent,
341          @QueryParam(TokenArgumentParam.NAME) @DefaultValue(TokenArgumentParam.DEFAULT)
342              final TokenArgumentParam delegationTokenArgument
343          ) throws IOException, InterruptedException {
344    
345        init(ugi, delegation, username, doAsUser, path, op, destination, owner,
346            group, permission, overwrite, bufferSize, replication, blockSize,
347            modificationTime, accessTime, renameOptions, delegationTokenArgument);
348    
349        return ugi.doAs(new PrivilegedExceptionAction<Response>() {
350          @Override
351          public Response run() throws IOException, URISyntaxException {
352            REMOTE_ADDRESS.set(request.getRemoteAddr());
353            try {
354              return put(ugi, delegation, username, doAsUser,
355                  path.getAbsolutePath(), op, destination, owner, group,
356                  permission, overwrite, bufferSize, replication, blockSize,
357                  modificationTime, accessTime, renameOptions, createParent,
358                  delegationTokenArgument);
359            } finally {
360              REMOTE_ADDRESS.set(null);
361            }
362          }
363        });
364      }
365    
366      private Response put(
367          final UserGroupInformation ugi,
368          final DelegationParam delegation,
369          final UserParam username,
370          final DoAsParam doAsUser,
371          final String fullpath,
372          final PutOpParam op,
373          final DestinationParam destination,
374          final OwnerParam owner,
375          final GroupParam group,
376          final PermissionParam permission,
377          final OverwriteParam overwrite,
378          final BufferSizeParam bufferSize,
379          final ReplicationParam replication,
380          final BlockSizeParam blockSize,
381          final ModificationTimeParam modificationTime,
382          final AccessTimeParam accessTime,
383          final RenameOptionSetParam renameOptions,
384          final CreateParentParam createParent,
385          final TokenArgumentParam delegationTokenArgument
386          ) throws IOException, URISyntaxException {
387    
388        final Configuration conf = (Configuration)context.getAttribute(JspHelper.CURRENT_CONF);
389        final NameNode namenode = (NameNode)context.getAttribute("name.node");
390        final NamenodeProtocols np = namenode.getRpcServer();
391    
392        switch(op.getValue()) {
393        case CREATE:
394        {
395          final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
396              fullpath, op.getValue(), -1L, blockSize.getValue(conf),
397              permission, overwrite, bufferSize, replication, blockSize);
398          return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
399        } 
400        case MKDIRS:
401        {
402          final boolean b = np.mkdirs(fullpath, permission.getFsPermission(), true);
403          final String js = JsonUtil.toJsonString("boolean", b);
404          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
405        }
406        case CREATESYMLINK:
407        {
408          np.createSymlink(destination.getValue(), fullpath,
409              PermissionParam.getDefaultFsPermission(), createParent.getValue());
410          return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
411        }
412        case RENAME:
413        {
414          final EnumSet<Options.Rename> s = renameOptions.getValue();
415          if (s.isEmpty()) {
416            final boolean b = np.rename(fullpath, destination.getValue());
417            final String js = JsonUtil.toJsonString("boolean", b);
418            return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
419          } else {
420            np.rename2(fullpath, destination.getValue(),
421                s.toArray(new Options.Rename[s.size()]));
422            return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
423          }
424        }
425        case SETREPLICATION:
426        {
427          final boolean b = np.setReplication(fullpath, replication.getValue(conf));
428          final String js = JsonUtil.toJsonString("boolean", b);
429          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
430        }
431        case SETOWNER:
432        {
433          if (owner.getValue() == null && group.getValue() == null) {
434            throw new IllegalArgumentException("Both owner and group are empty.");
435          }
436    
437          np.setOwner(fullpath, owner.getValue(), group.getValue());
438          return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
439        }
440        case SETPERMISSION:
441        {
442          np.setPermission(fullpath, permission.getFsPermission());
443          return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
444        }
445        case SETTIMES:
446        {
447          np.setTimes(fullpath, modificationTime.getValue(), accessTime.getValue());
448          return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
449        }
450        case RENEWDELEGATIONTOKEN:
451        {
452          final Token<DelegationTokenIdentifier> token = new Token<DelegationTokenIdentifier>();
453          token.decodeFromUrlString(delegationTokenArgument.getValue());
454          final long expiryTime = np.renewDelegationToken(token);
455          final String js = JsonUtil.toJsonString("long", expiryTime);
456          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
457        }
458        case CANCELDELEGATIONTOKEN:
459        {
460          final Token<DelegationTokenIdentifier> token = new Token<DelegationTokenIdentifier>();
461          token.decodeFromUrlString(delegationTokenArgument.getValue());
462          np.cancelDelegationToken(token);
463          return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
464        }
465        default:
466          throw new UnsupportedOperationException(op + " is not supported");
467        }
468      }
469    
470      /** Handle HTTP POST request for the root. */
471      @POST
472      @Path("/")
473      @Consumes({"*/*"})
474      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
475      public Response postRoot(
476          @Context final UserGroupInformation ugi,
477          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
478              final DelegationParam delegation,
479          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
480              final UserParam username,
481          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
482              final DoAsParam doAsUser,
483          @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT)
484              final PostOpParam op,
485          @QueryParam(ConcatSourcesParam.NAME) @DefaultValue(ConcatSourcesParam.DEFAULT)
486              final ConcatSourcesParam concatSrcs,
487          @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
488              final BufferSizeParam bufferSize
489          ) throws IOException, InterruptedException {
490        return post(ugi, delegation, username, doAsUser, ROOT, op, concatSrcs, bufferSize);
491      }
492    
493      /** Handle HTTP POST request. */
494      @POST
495      @Path("{" + UriFsPathParam.NAME + ":.*}")
496      @Consumes({"*/*"})
497      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
498      public Response post(
499          @Context final UserGroupInformation ugi,
500          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
501              final DelegationParam delegation,
502          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
503              final UserParam username,
504          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
505              final DoAsParam doAsUser,
506          @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
507          @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT)
508              final PostOpParam op,
509          @QueryParam(ConcatSourcesParam.NAME) @DefaultValue(ConcatSourcesParam.DEFAULT)
510              final ConcatSourcesParam concatSrcs,
511          @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
512              final BufferSizeParam bufferSize
513          ) throws IOException, InterruptedException {
514    
515        init(ugi, delegation, username, doAsUser, path, op, concatSrcs, bufferSize);
516    
517        return ugi.doAs(new PrivilegedExceptionAction<Response>() {
518          @Override
519          public Response run() throws IOException, URISyntaxException {
520            REMOTE_ADDRESS.set(request.getRemoteAddr());
521            try {
522              return post(ugi, delegation, username, doAsUser,
523                  path.getAbsolutePath(), op, concatSrcs, bufferSize);
524            } finally {
525              REMOTE_ADDRESS.set(null);
526            }
527          }
528        });
529      }
530    
531      private Response post(
532          final UserGroupInformation ugi,
533          final DelegationParam delegation,
534          final UserParam username,
535          final DoAsParam doAsUser,
536          final String fullpath,
537          final PostOpParam op,
538          final ConcatSourcesParam concatSrcs,
539          final BufferSizeParam bufferSize
540          ) throws IOException, URISyntaxException {
541        final NameNode namenode = (NameNode)context.getAttribute("name.node");
542    
543        switch(op.getValue()) {
544        case APPEND:
545        {
546          final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
547              fullpath, op.getValue(), -1L, -1L, bufferSize);
548          return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
549        }
550        case CONCAT:
551        {
552          namenode.getRpcServer().concat(fullpath, concatSrcs.getAbsolutePaths());
553          return Response.ok().build();
554        }
555        default:
556          throw new UnsupportedOperationException(op + " is not supported");
557        }
558      }
559    
560      /** Handle HTTP GET request for the root. */
561      @GET
562      @Path("/")
563      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
564      public Response getRoot(
565          @Context final UserGroupInformation ugi,
566          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
567              final DelegationParam delegation,
568          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
569              final UserParam username,
570          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
571              final DoAsParam doAsUser,
572          @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT)
573              final GetOpParam op,
574          @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT)
575              final OffsetParam offset,
576          @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT)
577              final LengthParam length,
578          @QueryParam(RenewerParam.NAME) @DefaultValue(RenewerParam.DEFAULT)
579              final RenewerParam renewer,
580          @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
581              final BufferSizeParam bufferSize
582          ) throws IOException, InterruptedException {
583        return get(ugi, delegation, username, doAsUser, ROOT, op,
584            offset, length, renewer, bufferSize);
585      }
586    
587      /** Handle HTTP GET request. */
588      @GET
589      @Path("{" + UriFsPathParam.NAME + ":.*}")
590      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
591      public Response get(
592          @Context final UserGroupInformation ugi,
593          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
594              final DelegationParam delegation,
595          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
596              final UserParam username,
597          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
598              final DoAsParam doAsUser,
599          @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
600          @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT)
601              final GetOpParam op,
602          @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT)
603              final OffsetParam offset,
604          @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT)
605              final LengthParam length,
606          @QueryParam(RenewerParam.NAME) @DefaultValue(RenewerParam.DEFAULT)
607              final RenewerParam renewer,
608          @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
609              final BufferSizeParam bufferSize
610          ) throws IOException, InterruptedException {
611    
612        init(ugi, delegation, username, doAsUser, path, op,
613            offset, length, renewer, bufferSize);
614    
615        return ugi.doAs(new PrivilegedExceptionAction<Response>() {
616          @Override
617          public Response run() throws IOException, URISyntaxException {
618            REMOTE_ADDRESS.set(request.getRemoteAddr());
619            try {
620              return get(ugi, delegation, username, doAsUser,
621                  path.getAbsolutePath(), op, offset, length, renewer, bufferSize);
622            } finally {
623              REMOTE_ADDRESS.set(null);
624            }
625          }
626        });
627      }
628    
629      private Response get(
630          final UserGroupInformation ugi,
631          final DelegationParam delegation,
632          final UserParam username,
633          final DoAsParam doAsUser,
634          final String fullpath,
635          final GetOpParam op,
636          final OffsetParam offset,
637          final LengthParam length,
638          final RenewerParam renewer,
639          final BufferSizeParam bufferSize
640          ) throws IOException, URISyntaxException {
641        final NameNode namenode = (NameNode)context.getAttribute("name.node");
642        final NamenodeProtocols np = namenode.getRpcServer();
643    
644        switch(op.getValue()) {
645        case OPEN:
646        {
647          final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
648              fullpath, op.getValue(), offset.getValue(), -1L, offset, length, bufferSize);
649          return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
650        }
651        case GET_BLOCK_LOCATIONS:
652        {
653          final long offsetValue = offset.getValue();
654          final Long lengthValue = length.getValue();
655          final LocatedBlocks locatedblocks = np.getBlockLocations(fullpath,
656              offsetValue, lengthValue != null? lengthValue: Long.MAX_VALUE);
657          final String js = JsonUtil.toJsonString(locatedblocks);
658          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
659        }
660        case GETFILESTATUS:
661        {
662          final HdfsFileStatus status = np.getFileInfo(fullpath);
663          if (status == null) {
664            throw new FileNotFoundException("File does not exist: " + fullpath);
665          }
666    
667          final String js = JsonUtil.toJsonString(status, true);
668          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
669        }
670        case LISTSTATUS:
671        {
672          final StreamingOutput streaming = getListingStream(np, fullpath);
673          return Response.ok(streaming).type(MediaType.APPLICATION_JSON).build();
674        }
675        case GETCONTENTSUMMARY:
676        {
677          final ContentSummary contentsummary = np.getContentSummary(fullpath);
678          final String js = JsonUtil.toJsonString(contentsummary);
679          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
680        }
681        case GETFILECHECKSUM:
682        {
683          final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
684              fullpath, op.getValue(), -1L, -1L);
685          return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
686        }
687        case GETDELEGATIONTOKEN:
688        {
689          if (delegation.getValue() != null) {
690            throw new IllegalArgumentException(delegation.getName()
691                + " parameter is not null.");
692          }
693          final Token<? extends TokenIdentifier> token = generateDelegationToken(
694              namenode, ugi, renewer.getValue());
695          final String js = JsonUtil.toJsonString(token);
696          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
697        }
698        case GETHOMEDIRECTORY:
699        {
700          final String js = JsonUtil.toJsonString(
701              org.apache.hadoop.fs.Path.class.getSimpleName(),
702              WebHdfsFileSystem.getHomeDirectoryString(ugi));
703          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
704        }
705        default:
706          throw new UnsupportedOperationException(op + " is not supported");
707        }
708      }
709    
710      private static DirectoryListing getDirectoryListing(final NamenodeProtocols np,
711          final String p, byte[] startAfter) throws IOException {
712        final DirectoryListing listing = np.getListing(p, startAfter, false);
713        if (listing == null) { // the directory does not exist
714          throw new FileNotFoundException("File " + p + " does not exist.");
715        }
716        return listing;
717      }
718      
719      private static StreamingOutput getListingStream(final NamenodeProtocols np, 
720          final String p) throws IOException {
721        // allows exceptions like FNF or ACE to prevent http response of 200 for
722        // a failure since we can't (currently) return error responses in the
723        // middle of a streaming operation
724        final DirectoryListing firstDirList = getDirectoryListing(np, p,
725            HdfsFileStatus.EMPTY_NAME);
726    
727        // must save ugi because the streaming object will be executed outside
728        // the remote user's ugi
729        final UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
730        return new StreamingOutput() {
731          @Override
732          public void write(final OutputStream outstream) throws IOException {
733            final PrintWriter out = new PrintWriter(new OutputStreamWriter(
734                outstream, Charsets.UTF_8));
735            out.println("{\"" + FileStatus.class.getSimpleName() + "es\":{\""
736                + FileStatus.class.getSimpleName() + "\":[");
737    
738            try {
739              // restore remote user's ugi
740              ugi.doAs(new PrivilegedExceptionAction<Void>() {
741                @Override
742                public Void run() throws IOException {
743                  long n = 0;
744                  for (DirectoryListing dirList = firstDirList; ;
745                       dirList = getDirectoryListing(np, p, dirList.getLastName())
746                  ) {
747                    // send each segment of the directory listing
748                    for (HdfsFileStatus s : dirList.getPartialListing()) {
749                      if (n++ > 0) {
750                        out.println(',');
751                      }
752                      out.print(JsonUtil.toJsonString(s, false));
753                    }
754                    // stop if last segment
755                    if (!dirList.hasMore()) {
756                      break;
757                    }
758                  }
759                  return null;
760                }
761              });
762            } catch (InterruptedException e) {
763              throw new IOException(e);
764            }
765            
766            out.println();
767            out.println("]}}");
768            out.flush();
769          }
770        };
771      }
772    
773      /** Handle HTTP DELETE request for the root. */
774      @DELETE
775      @Path("/")
776      @Produces(MediaType.APPLICATION_JSON)
777      public Response deleteRoot(
778          @Context final UserGroupInformation ugi,
779          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
780              final DelegationParam delegation,
781          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
782              final UserParam username,
783          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
784              final DoAsParam doAsUser,
785          @QueryParam(DeleteOpParam.NAME) @DefaultValue(DeleteOpParam.DEFAULT)
786              final DeleteOpParam op,
787          @QueryParam(RecursiveParam.NAME) @DefaultValue(RecursiveParam.DEFAULT)
788              final RecursiveParam recursive
789          ) throws IOException, InterruptedException {
790        return delete(ugi, delegation, username, doAsUser, ROOT, op, recursive);
791      }
792    
793      /** Handle HTTP DELETE request. */
794      @DELETE
795      @Path("{" + UriFsPathParam.NAME + ":.*}")
796      @Produces(MediaType.APPLICATION_JSON)
797      public Response delete(
798          @Context final UserGroupInformation ugi,
799          @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
800              final DelegationParam delegation,
801          @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
802              final UserParam username,
803          @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
804              final DoAsParam doAsUser,
805          @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
806          @QueryParam(DeleteOpParam.NAME) @DefaultValue(DeleteOpParam.DEFAULT)
807              final DeleteOpParam op,
808          @QueryParam(RecursiveParam.NAME) @DefaultValue(RecursiveParam.DEFAULT)
809              final RecursiveParam recursive
810          ) throws IOException, InterruptedException {
811    
812        init(ugi, delegation, username, doAsUser, path, op, recursive);
813    
814        return ugi.doAs(new PrivilegedExceptionAction<Response>() {
815          @Override
816          public Response run() throws IOException {
817            REMOTE_ADDRESS.set(request.getRemoteAddr());
818            try {
819              return delete(ugi, delegation, username, doAsUser,
820                  path.getAbsolutePath(), op, recursive);
821            } finally {
822              REMOTE_ADDRESS.set(null);
823            }
824          }
825        });
826      }
827    
828      private Response delete(
829          final UserGroupInformation ugi,
830          final DelegationParam delegation,
831          final UserParam username,
832          final DoAsParam doAsUser,
833          final String fullpath,
834          final DeleteOpParam op,
835          final RecursiveParam recursive
836          ) throws IOException {
837        final NameNode namenode = (NameNode)context.getAttribute("name.node");
838    
839        switch(op.getValue()) {
840        case DELETE:
841        {
842          final boolean b = namenode.getRpcServer().delete(fullpath, recursive.getValue());
843          final String js = JsonUtil.toJsonString("boolean", b);
844          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
845        }
846        default:
847          throw new UnsupportedOperationException(op + " is not supported");
848        }
849      }
850    }