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    
019    package org.apache.hadoop.fs.http.server;
020    
021    import org.apache.hadoop.classification.InterfaceAudience;
022    import org.apache.hadoop.conf.Configuration;
023    import org.apache.hadoop.fs.FileSystem;
024    import org.apache.hadoop.fs.http.client.HttpFSFileSystem;
025    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.AccessTimeParam;
026    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.BlockSizeParam;
027    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DataParam;
028    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DestinationParam;
029    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DoAsParam;
030    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.FilterParam;
031    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.GroupParam;
032    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.LenParam;
033    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.ModifiedTimeParam;
034    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OffsetParam;
035    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OperationParam;
036    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OverwriteParam;
037    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OwnerParam;
038    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.PermissionParam;
039    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.RecursiveParam;
040    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.ReplicationParam;
041    import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.SourcesParam;
042    import org.apache.hadoop.lib.service.FileSystemAccess;
043    import org.apache.hadoop.lib.service.FileSystemAccessException;
044    import org.apache.hadoop.lib.service.Groups;
045    import org.apache.hadoop.lib.service.Instrumentation;
046    import org.apache.hadoop.lib.service.ProxyUser;
047    import org.apache.hadoop.lib.servlet.FileSystemReleaseFilter;
048    import org.apache.hadoop.lib.servlet.HostnameFilter;
049    import org.apache.hadoop.lib.wsrs.InputStreamEntity;
050    import org.apache.hadoop.lib.wsrs.Parameters;
051    import org.apache.hadoop.security.authentication.server.AuthenticationToken;
052    import org.json.simple.JSONObject;
053    import org.slf4j.Logger;
054    import org.slf4j.LoggerFactory;
055    import org.slf4j.MDC;
056    
057    import javax.ws.rs.Consumes;
058    import javax.ws.rs.DELETE;
059    import javax.ws.rs.GET;
060    import javax.ws.rs.POST;
061    import javax.ws.rs.PUT;
062    import javax.ws.rs.Path;
063    import javax.ws.rs.PathParam;
064    import javax.ws.rs.Produces;
065    import javax.ws.rs.QueryParam;
066    import javax.ws.rs.core.Context;
067    import javax.ws.rs.core.MediaType;
068    import javax.ws.rs.core.Response;
069    import javax.ws.rs.core.UriBuilder;
070    import javax.ws.rs.core.UriInfo;
071    import java.io.IOException;
072    import java.io.InputStream;
073    import java.net.URI;
074    import java.security.AccessControlException;
075    import java.security.Principal;
076    import java.text.MessageFormat;
077    import java.util.List;
078    import java.util.Map;
079    
080    /**
081     * Main class of HttpFSServer server.
082     * <p/>
083     * The <code>HttpFSServer</code> class uses Jersey JAX-RS to binds HTTP requests to the
084     * different operations.
085     */
086    @Path(HttpFSFileSystem.SERVICE_VERSION)
087    @InterfaceAudience.Private
088    public class HttpFSServer {
089      private static Logger AUDIT_LOG = LoggerFactory.getLogger("httpfsaudit");
090    
091      /**
092       * Resolves the effective user that will be used to request a FileSystemAccess filesystem.
093       * <p/>
094       * If the doAs-user is NULL or the same as the user, it returns the user.
095       * <p/>
096       * Otherwise it uses proxyuser rules (see {@link ProxyUser} to determine if the
097       * current user can impersonate the doAs-user.
098       * <p/>
099       * If the current user cannot impersonate the doAs-user an
100       * <code>AccessControlException</code> will be thrown.
101       *
102       * @param user principal for whom the filesystem instance is.
103       * @param doAs do-as user, if any.
104       *
105       * @return the effective user.
106       *
107       * @throws IOException thrown if an IO error occurrs.
108       * @throws AccessControlException thrown if the current user cannot impersonate
109       * the doAs-user.
110       */
111      private String getEffectiveUser(Principal user, String doAs) throws IOException {
112        String effectiveUser = user.getName();
113        if (doAs != null && !doAs.equals(user.getName())) {
114          ProxyUser proxyUser = HttpFSServerWebApp.get().get(ProxyUser.class);
115          String proxyUserName;
116          if (user instanceof AuthenticationToken) {
117            proxyUserName = ((AuthenticationToken)user).getUserName();
118          } else {
119            proxyUserName = user.getName();
120          }
121          proxyUser.validate(proxyUserName, HostnameFilter.get(), doAs);
122          effectiveUser = doAs;
123          AUDIT_LOG.info("Proxy user [{}] DoAs user [{}]", proxyUserName, doAs);
124        }
125        return effectiveUser;
126      }
127    
128      /**
129       * Executes a {@link FileSystemAccess.FileSystemExecutor} using a filesystem for the effective
130       * user.
131       *
132       * @param user principal making the request.
133       * @param doAs do-as user, if any.
134       * @param executor FileSystemExecutor to execute.
135       *
136       * @return FileSystemExecutor response
137       *
138       * @throws IOException thrown if an IO error occurrs.
139       * @throws FileSystemAccessException thrown if a FileSystemAccess releated error occurred. Thrown
140       * exceptions are handled by {@link HttpFSExceptionProvider}.
141       */
142      private <T> T fsExecute(Principal user, String doAs, FileSystemAccess.FileSystemExecutor<T> executor)
143        throws IOException, FileSystemAccessException {
144        String hadoopUser = getEffectiveUser(user, doAs);
145        FileSystemAccess fsAccess = HttpFSServerWebApp.get().get(FileSystemAccess.class);
146        Configuration conf = HttpFSServerWebApp.get().get(FileSystemAccess.class).getFileSystemConfiguration();
147        return fsAccess.execute(hadoopUser, conf, executor);
148      }
149    
150      /**
151       * Returns a filesystem instance. The fileystem instance is wired for release at the completion of
152       * the current Servlet request via the {@link FileSystemReleaseFilter}.
153       * <p/>
154       * If a do-as user is specified, the current user must be a valid proxyuser, otherwise an
155       * <code>AccessControlException</code> will be thrown.
156       *
157       * @param user principal for whom the filesystem instance is.
158       * @param doAs do-as user, if any.
159       *
160       * @return a filesystem for the specified user or do-as user.
161       *
162       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
163       * handled by {@link HttpFSExceptionProvider}.
164       * @throws FileSystemAccessException thrown if a FileSystemAccess releated error occurred. Thrown
165       * exceptions are handled by {@link HttpFSExceptionProvider}.
166       */
167      private FileSystem createFileSystem(Principal user, String doAs) throws IOException, FileSystemAccessException {
168        String hadoopUser = getEffectiveUser(user, doAs);
169        FileSystemAccess fsAccess = HttpFSServerWebApp.get().get(FileSystemAccess.class);
170        Configuration conf = HttpFSServerWebApp.get().get(FileSystemAccess.class).getFileSystemConfiguration();
171        FileSystem fs = fsAccess.createFileSystem(hadoopUser, conf);
172        FileSystemReleaseFilter.setFileSystem(fs);
173        return fs;
174      }
175    
176      private void enforceRootPath(HttpFSFileSystem.Operation op, String path) {
177        if (!path.equals("/")) {
178          throw new UnsupportedOperationException(
179            MessageFormat.format("Operation [{0}], invalid path [{1}], must be '/'",
180                                 op, path));
181        }
182      }
183    
184      /**
185       * Special binding for '/' as it is not handled by the wildcard binding.
186       *
187       * @param user the principal of the user making the request.
188       * @param op the HttpFS operation of the request.
189       * @param params the HttpFS parameters of the request.
190       *
191       * @return the request response.
192       *
193       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
194       * handled by {@link HttpFSExceptionProvider}.
195       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
196       * error occurred. Thrown exceptions are handled by
197       * {@link HttpFSExceptionProvider}.
198       */
199      @GET
200      @Produces(MediaType.APPLICATION_JSON)
201      public Response getRoot(@Context Principal user,
202                              @QueryParam(OperationParam.NAME) OperationParam op,
203                              @Context Parameters params)
204        throws IOException, FileSystemAccessException {
205        return get(user, "", op, params);
206      }
207    
208      private String makeAbsolute(String path) {
209        return "/" + ((path != null) ? path : "");
210      }
211    
212      /**
213       * Binding to handle GET requests, supported operations are
214       *
215       * @param user the principal of the user making the request.
216       * @param path the path for operation.
217       * @param op the HttpFS operation of the request.
218       * @param params the HttpFS parameters of the request.
219       *
220       * @return the request response.
221       *
222       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
223       * handled by {@link HttpFSExceptionProvider}.
224       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
225       * error occurred. Thrown exceptions are handled by
226       * {@link HttpFSExceptionProvider}.
227       */
228      @GET
229      @Path("{path:.*}")
230      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
231      public Response get(@Context Principal user,
232                          @PathParam("path") String path,
233                          @QueryParam(OperationParam.NAME) OperationParam op,
234                          @Context Parameters params)
235        throws IOException, FileSystemAccessException {
236        Response response;
237        path = makeAbsolute(path);
238        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
239        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
240        switch (op.value()) {
241          case OPEN: {
242            //Invoking the command directly using an unmanaged FileSystem that is
243            // released by the FileSystemReleaseFilter
244            FSOperations.FSOpen command = new FSOperations.FSOpen(path);
245            FileSystem fs = createFileSystem(user, doAs);
246            InputStream is = command.execute(fs);
247            Long offset = params.get(OffsetParam.NAME, OffsetParam.class);
248            Long len = params.get(LenParam.NAME, LenParam.class);
249            AUDIT_LOG.info("[{}] offset [{}] len [{}]",
250                           new Object[]{path, offset, len});
251            InputStreamEntity entity = new InputStreamEntity(is, offset, len);
252            response =
253              Response.ok(entity).type(MediaType.APPLICATION_OCTET_STREAM).build();
254            break;
255          }
256          case GETFILESTATUS: {
257            FSOperations.FSFileStatus command =
258              new FSOperations.FSFileStatus(path);
259            Map json = fsExecute(user, doAs, command);
260            AUDIT_LOG.info("[{}]", path);
261            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
262            break;
263          }
264          case LISTSTATUS: {
265            String filter = params.get(FilterParam.NAME, FilterParam.class);
266            FSOperations.FSListStatus command = new FSOperations.FSListStatus(
267              path, filter);
268            Map json = fsExecute(user, doAs, command);
269            AUDIT_LOG.info("[{}] filter [{}]", path,
270                           (filter != null) ? filter : "-");
271            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
272            break;
273          }
274          case GETHOMEDIRECTORY: {
275            enforceRootPath(op.value(), path);
276            FSOperations.FSHomeDir command = new FSOperations.FSHomeDir();
277            JSONObject json = fsExecute(user, doAs, command);
278            AUDIT_LOG.info("");
279            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
280            break;
281          }
282          case INSTRUMENTATION: {
283            enforceRootPath(op.value(), path);
284            Groups groups = HttpFSServerWebApp.get().get(Groups.class);
285            List<String> userGroups = groups.getGroups(user.getName());
286            if (!userGroups.contains(HttpFSServerWebApp.get().getAdminGroup())) {
287              throw new AccessControlException(
288                "User not in HttpFSServer admin group");
289            }
290            Instrumentation instrumentation =
291              HttpFSServerWebApp.get().get(Instrumentation.class);
292            Map snapshot = instrumentation.getSnapshot();
293            response = Response.ok(snapshot).build();
294            break;
295          }
296          case GETCONTENTSUMMARY: {
297            FSOperations.FSContentSummary command =
298              new FSOperations.FSContentSummary(path);
299            Map json = fsExecute(user, doAs, command);
300            AUDIT_LOG.info("[{}]", path);
301            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
302            break;
303          }
304          case GETFILECHECKSUM: {
305            FSOperations.FSFileChecksum command =
306              new FSOperations.FSFileChecksum(path);
307            Map json = fsExecute(user, doAs, command);
308            AUDIT_LOG.info("[{}]", path);
309            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
310            break;
311          }
312          case GETFILEBLOCKLOCATIONS: {
313            response = Response.status(Response.Status.BAD_REQUEST).build();
314            break;
315          }
316          default: {
317            throw new IOException(
318              MessageFormat.format("Invalid HTTP GET operation [{0}]",
319                                   op.value()));
320          }
321        }
322        return response;
323      }
324    
325    
326      /**
327       * Binding to handle DELETE requests.
328       *
329       * @param user the principal of the user making the request.
330       * @param path the path for operation.
331       * @param op the HttpFS operation of the request.
332       * @param params the HttpFS parameters of the request.
333       *
334       * @return the request response.
335       *
336       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
337       * handled by {@link HttpFSExceptionProvider}.
338       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
339       * error occurred. Thrown exceptions are handled by
340       * {@link HttpFSExceptionProvider}.
341       */
342      @DELETE
343      @Path("{path:.*}")
344      @Produces(MediaType.APPLICATION_JSON)
345      public Response delete(@Context Principal user,
346                          @PathParam("path") String path,
347                          @QueryParam(OperationParam.NAME) OperationParam op,
348                          @Context Parameters params)
349        throws IOException, FileSystemAccessException {
350        Response response;
351        path = makeAbsolute(path);
352        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
353        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
354        switch (op.value()) {
355          case DELETE: {
356            Boolean recursive =
357              params.get(RecursiveParam.NAME,  RecursiveParam.class);
358            AUDIT_LOG.info("[{}] recursive [{}]", path, recursive);
359            FSOperations.FSDelete command =
360              new FSOperations.FSDelete(path, recursive);
361            JSONObject json = fsExecute(user, doAs, command);
362            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
363            break;
364          }
365          default: {
366            throw new IOException(
367              MessageFormat.format("Invalid HTTP DELETE operation [{0}]",
368                                   op.value()));
369          }
370        }
371        return response;
372      }
373    
374      /**
375       * Binding to handle POST requests.
376       *
377       * @param is the inputstream for the request payload.
378       * @param user the principal of the user making the request.
379       * @param uriInfo the of the request.
380       * @param path the path for operation.
381       * @param op the HttpFS operation of the request.
382       * @param params the HttpFS parameters of the request.
383       *
384       * @return the request response.
385       *
386       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
387       * handled by {@link HttpFSExceptionProvider}.
388       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
389       * error occurred. Thrown exceptions are handled by
390       * {@link HttpFSExceptionProvider}.
391       */
392      @POST
393      @Path("{path:.*}")
394      @Consumes({"*/*"})
395      @Produces({MediaType.APPLICATION_JSON})
396      public Response post(InputStream is,
397                           @Context Principal user,
398                           @Context UriInfo uriInfo,
399                           @PathParam("path") String path,
400                           @QueryParam(OperationParam.NAME) OperationParam op,
401                           @Context Parameters params)
402        throws IOException, FileSystemAccessException {
403        Response response;
404        path = makeAbsolute(path);
405        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
406        switch (op.value()) {
407          case APPEND: {
408            String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
409            Boolean hasData = params.get(DataParam.NAME, DataParam.class);
410            if (!hasData) {
411              response = Response.temporaryRedirect(
412                createUploadRedirectionURL(uriInfo,
413                  HttpFSFileSystem.Operation.APPEND)).build();
414            } else {
415              FSOperations.FSAppend command =
416                new FSOperations.FSAppend(is, path);
417              fsExecute(user, doAs, command);
418              AUDIT_LOG.info("[{}]", path);
419              response = Response.ok().type(MediaType.APPLICATION_JSON).build();
420            }
421            break;
422          }
423          case CONCAT: {
424            System.out.println("HTTPFS SERVER CONCAT");
425            String sources = params.get(SourcesParam.NAME, SourcesParam.class);
426    
427            FSOperations.FSConcat command =
428                new FSOperations.FSConcat(path, sources.split(","));
429            fsExecute(user, null, command);
430            AUDIT_LOG.info("[{}]", path);
431            System.out.println("SENT RESPONSE");
432            response = Response.ok().build();
433            break;
434          }
435          default: {
436            throw new IOException(
437              MessageFormat.format("Invalid HTTP POST operation [{0}]",
438                                   op.value()));
439          }
440        }
441        return response;
442      }
443    
444      /**
445       * Creates the URL for an upload operation (create or append).
446       *
447       * @param uriInfo uri info of the request.
448       * @param uploadOperation operation for the upload URL.
449       *
450       * @return the URI for uploading data.
451       */
452      protected URI createUploadRedirectionURL(UriInfo uriInfo, Enum<?> uploadOperation) {
453        UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
454        uriBuilder = uriBuilder.replaceQueryParam(OperationParam.NAME, uploadOperation).
455          queryParam(DataParam.NAME, Boolean.TRUE);
456        return uriBuilder.build(null);
457      }
458    
459    
460      /**
461       * Binding to handle PUT requests.
462       *
463       * @param is the inputstream for the request payload.
464       * @param user the principal of the user making the request.
465       * @param uriInfo the of the request.
466       * @param path the path for operation.
467       * @param op the HttpFS operation of the request.
468       * @param params the HttpFS parameters of the request.
469       *
470       * @return the request response.
471       *
472       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
473       * handled by {@link HttpFSExceptionProvider}.
474       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
475       * error occurred. Thrown exceptions are handled by
476       * {@link HttpFSExceptionProvider}.
477       */
478      @PUT
479      @Path("{path:.*}")
480      @Consumes({"*/*"})
481      @Produces({MediaType.APPLICATION_JSON})
482      public Response put(InputStream is,
483                           @Context Principal user,
484                           @Context UriInfo uriInfo,
485                           @PathParam("path") String path,
486                           @QueryParam(OperationParam.NAME) OperationParam op,
487                           @Context Parameters params)
488        throws IOException, FileSystemAccessException {
489        Response response;
490        path = makeAbsolute(path);
491        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
492        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
493        switch (op.value()) {
494          case CREATE: {
495            Boolean hasData = params.get(DataParam.NAME, DataParam.class);
496            if (!hasData) {
497              response = Response.temporaryRedirect(
498                createUploadRedirectionURL(uriInfo,
499                  HttpFSFileSystem.Operation.CREATE)).build();
500            } else {
501              Short permission = params.get(PermissionParam.NAME,
502                                             PermissionParam.class);
503              Boolean override = params.get(OverwriteParam.NAME,
504                                            OverwriteParam.class);
505              Short replication = params.get(ReplicationParam.NAME,
506                                             ReplicationParam.class);
507              Long blockSize = params.get(BlockSizeParam.NAME,
508                                          BlockSizeParam.class);
509              FSOperations.FSCreate command =
510                new FSOperations.FSCreate(is, path, permission, override,
511                                          replication, blockSize);
512              fsExecute(user, doAs, command);
513              AUDIT_LOG.info(
514                "[{}] permission [{}] override [{}] replication [{}] blockSize [{}]",
515                new Object[]{path, permission, override, replication, blockSize});
516              response = Response.status(Response.Status.CREATED).build();
517            }
518            break;
519          }
520          case MKDIRS: {
521            Short permission = params.get(PermissionParam.NAME,
522                                           PermissionParam.class);
523            FSOperations.FSMkdirs command =
524              new FSOperations.FSMkdirs(path, permission);
525            JSONObject json = fsExecute(user, doAs, command);
526            AUDIT_LOG.info("[{}] permission [{}]", path, permission);
527            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
528            break;
529          }
530          case RENAME: {
531            String toPath = params.get(DestinationParam.NAME, DestinationParam.class);
532            FSOperations.FSRename command =
533              new FSOperations.FSRename(path, toPath);
534            JSONObject json = fsExecute(user, doAs, command);
535            AUDIT_LOG.info("[{}] to [{}]", path, toPath);
536            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
537            break;
538          }
539          case SETOWNER: {
540            String owner = params.get(OwnerParam.NAME, OwnerParam.class);
541            String group = params.get(GroupParam.NAME, GroupParam.class);
542            FSOperations.FSSetOwner command =
543              new FSOperations.FSSetOwner(path, owner, group);
544            fsExecute(user, doAs, command);
545            AUDIT_LOG.info("[{}] to (O/G)[{}]", path, owner + ":" + group);
546            response = Response.ok().build();
547            break;
548          }
549          case SETPERMISSION: {
550            Short permission = params.get(PermissionParam.NAME,
551                                          PermissionParam.class);
552            FSOperations.FSSetPermission command =
553              new FSOperations.FSSetPermission(path, permission);
554            fsExecute(user, doAs, command);
555            AUDIT_LOG.info("[{}] to [{}]", path, permission);
556            response = Response.ok().build();
557            break;
558          }
559          case SETREPLICATION: {
560            Short replication = params.get(ReplicationParam.NAME,
561                                           ReplicationParam.class);
562            FSOperations.FSSetReplication command =
563              new FSOperations.FSSetReplication(path, replication);
564            JSONObject json = fsExecute(user, doAs, command);
565            AUDIT_LOG.info("[{}] to [{}]", path, replication);
566            response = Response.ok(json).build();
567            break;
568          }
569          case SETTIMES: {
570            Long modifiedTime = params.get(ModifiedTimeParam.NAME,
571                                           ModifiedTimeParam.class);
572            Long accessTime = params.get(AccessTimeParam.NAME,
573                                         AccessTimeParam.class);
574            FSOperations.FSSetTimes command =
575              new FSOperations.FSSetTimes(path, modifiedTime, accessTime);
576            fsExecute(user, doAs, command);
577            AUDIT_LOG.info("[{}] to (M/A)[{}]", path,
578                           modifiedTime + ":" + accessTime);
579            response = Response.ok().build();
580            break;
581          }
582          default: {
583            throw new IOException(
584              MessageFormat.format("Invalid HTTP PUT operation [{0}]",
585                                   op.value()));
586          }
587        }
588        return response;
589      }
590    
591    }