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