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