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      @Path("/")
201      @Produces(MediaType.APPLICATION_JSON)
202      public Response getRoot(@Context Principal user,
203                              @QueryParam(OperationParam.NAME) OperationParam op,
204                              @Context Parameters params)
205        throws IOException, FileSystemAccessException {
206        return get(user, "", op, params);
207      }
208    
209      private String makeAbsolute(String path) {
210        return "/" + ((path != null) ? path : "");
211      }
212    
213      /**
214       * Binding to handle GET requests, supported operations are
215       *
216       * @param user the principal of the user making the request.
217       * @param path the path for operation.
218       * @param op the HttpFS operation of the request.
219       * @param params the HttpFS parameters of the request.
220       *
221       * @return the request response.
222       *
223       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
224       * handled by {@link HttpFSExceptionProvider}.
225       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
226       * error occurred. Thrown exceptions are handled by
227       * {@link HttpFSExceptionProvider}.
228       */
229      @GET
230      @Path("{path:.*}")
231      @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
232      public Response get(@Context Principal user,
233                          @PathParam("path") String path,
234                          @QueryParam(OperationParam.NAME) OperationParam op,
235                          @Context Parameters params)
236        throws IOException, FileSystemAccessException {
237        Response response;
238        path = makeAbsolute(path);
239        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
240        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
241        switch (op.value()) {
242          case OPEN: {
243            //Invoking the command directly using an unmanaged FileSystem that is
244            // released by the FileSystemReleaseFilter
245            FSOperations.FSOpen command = new FSOperations.FSOpen(path);
246            FileSystem fs = createFileSystem(user, doAs);
247            InputStream is = command.execute(fs);
248            Long offset = params.get(OffsetParam.NAME, OffsetParam.class);
249            Long len = params.get(LenParam.NAME, LenParam.class);
250            AUDIT_LOG.info("[{}] offset [{}] len [{}]",
251                           new Object[]{path, offset, len});
252            InputStreamEntity entity = new InputStreamEntity(is, offset, len);
253            response =
254              Response.ok(entity).type(MediaType.APPLICATION_OCTET_STREAM).build();
255            break;
256          }
257          case GETFILESTATUS: {
258            FSOperations.FSFileStatus command =
259              new FSOperations.FSFileStatus(path);
260            Map json = fsExecute(user, doAs, command);
261            AUDIT_LOG.info("[{}]", path);
262            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
263            break;
264          }
265          case LISTSTATUS: {
266            String filter = params.get(FilterParam.NAME, FilterParam.class);
267            FSOperations.FSListStatus command = new FSOperations.FSListStatus(
268              path, filter);
269            Map json = fsExecute(user, doAs, command);
270            AUDIT_LOG.info("[{}] filter [{}]", path,
271                           (filter != null) ? filter : "-");
272            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
273            break;
274          }
275          case GETHOMEDIRECTORY: {
276            enforceRootPath(op.value(), path);
277            FSOperations.FSHomeDir command = new FSOperations.FSHomeDir();
278            JSONObject json = fsExecute(user, doAs, command);
279            AUDIT_LOG.info("");
280            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
281            break;
282          }
283          case INSTRUMENTATION: {
284            enforceRootPath(op.value(), path);
285            Groups groups = HttpFSServerWebApp.get().get(Groups.class);
286            List<String> userGroups = groups.getGroups(user.getName());
287            if (!userGroups.contains(HttpFSServerWebApp.get().getAdminGroup())) {
288              throw new AccessControlException(
289                "User not in HttpFSServer admin group");
290            }
291            Instrumentation instrumentation =
292              HttpFSServerWebApp.get().get(Instrumentation.class);
293            Map snapshot = instrumentation.getSnapshot();
294            response = Response.ok(snapshot).build();
295            break;
296          }
297          case GETCONTENTSUMMARY: {
298            FSOperations.FSContentSummary command =
299              new FSOperations.FSContentSummary(path);
300            Map json = fsExecute(user, doAs, command);
301            AUDIT_LOG.info("[{}]", path);
302            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
303            break;
304          }
305          case GETFILECHECKSUM: {
306            FSOperations.FSFileChecksum command =
307              new FSOperations.FSFileChecksum(path);
308            Map json = fsExecute(user, doAs, command);
309            AUDIT_LOG.info("[{}]", path);
310            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
311            break;
312          }
313          case GETFILEBLOCKLOCATIONS: {
314            response = Response.status(Response.Status.BAD_REQUEST).build();
315            break;
316          }
317          default: {
318            throw new IOException(
319              MessageFormat.format("Invalid HTTP GET operation [{0}]",
320                                   op.value()));
321          }
322        }
323        return response;
324      }
325    
326    
327      /**
328       * Binding to handle DELETE requests.
329       *
330       * @param user the principal of the user making the request.
331       * @param path the path for operation.
332       * @param op the HttpFS operation of the request.
333       * @param params the HttpFS parameters of the request.
334       *
335       * @return the request response.
336       *
337       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
338       * handled by {@link HttpFSExceptionProvider}.
339       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
340       * error occurred. Thrown exceptions are handled by
341       * {@link HttpFSExceptionProvider}.
342       */
343      @DELETE
344      @Path("{path:.*}")
345      @Produces(MediaType.APPLICATION_JSON)
346      public Response delete(@Context Principal user,
347                          @PathParam("path") String path,
348                          @QueryParam(OperationParam.NAME) OperationParam op,
349                          @Context Parameters params)
350        throws IOException, FileSystemAccessException {
351        Response response;
352        path = makeAbsolute(path);
353        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
354        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
355        switch (op.value()) {
356          case DELETE: {
357            Boolean recursive =
358              params.get(RecursiveParam.NAME,  RecursiveParam.class);
359            AUDIT_LOG.info("[{}] recursive [{}]", path, recursive);
360            FSOperations.FSDelete command =
361              new FSOperations.FSDelete(path, recursive);
362            JSONObject json = fsExecute(user, doAs, command);
363            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
364            break;
365          }
366          default: {
367            throw new IOException(
368              MessageFormat.format("Invalid HTTP DELETE operation [{0}]",
369                                   op.value()));
370          }
371        }
372        return response;
373      }
374    
375      /**
376       * Binding to handle POST requests.
377       *
378       * @param is the inputstream for the request payload.
379       * @param user the principal of the user making the request.
380       * @param uriInfo the of the request.
381       * @param path the path for operation.
382       * @param op the HttpFS operation of the request.
383       * @param params the HttpFS parameters of the request.
384       *
385       * @return the request response.
386       *
387       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
388       * handled by {@link HttpFSExceptionProvider}.
389       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
390       * error occurred. Thrown exceptions are handled by
391       * {@link HttpFSExceptionProvider}.
392       */
393      @POST
394      @Path("{path:.*}")
395      @Consumes({"*/*"})
396      @Produces({MediaType.APPLICATION_JSON})
397      public Response post(InputStream is,
398                           @Context Principal user,
399                           @Context UriInfo uriInfo,
400                           @PathParam("path") String path,
401                           @QueryParam(OperationParam.NAME) OperationParam op,
402                           @Context Parameters params)
403        throws IOException, FileSystemAccessException {
404        Response response;
405        path = makeAbsolute(path);
406        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
407        switch (op.value()) {
408          case APPEND: {
409            String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
410            Boolean hasData = params.get(DataParam.NAME, DataParam.class);
411            if (!hasData) {
412              response = Response.temporaryRedirect(
413                createUploadRedirectionURL(uriInfo,
414                  HttpFSFileSystem.Operation.APPEND)).build();
415            } else {
416              FSOperations.FSAppend command =
417                new FSOperations.FSAppend(is, path);
418              fsExecute(user, doAs, command);
419              AUDIT_LOG.info("[{}]", path);
420              response = Response.ok().type(MediaType.APPLICATION_JSON).build();
421            }
422            break;
423          }
424          case CONCAT: {
425            System.out.println("HTTPFS SERVER CONCAT");
426            String sources = params.get(SourcesParam.NAME, SourcesParam.class);
427    
428            FSOperations.FSConcat command =
429                new FSOperations.FSConcat(path, sources.split(","));
430            fsExecute(user, null, command);
431            AUDIT_LOG.info("[{}]", path);
432            System.out.println("SENT RESPONSE");
433            response = Response.ok().build();
434            break;
435          }
436          default: {
437            throw new IOException(
438              MessageFormat.format("Invalid HTTP POST operation [{0}]",
439                                   op.value()));
440          }
441        }
442        return response;
443      }
444    
445      /**
446       * Creates the URL for an upload operation (create or append).
447       *
448       * @param uriInfo uri info of the request.
449       * @param uploadOperation operation for the upload URL.
450       *
451       * @return the URI for uploading data.
452       */
453      protected URI createUploadRedirectionURL(UriInfo uriInfo, Enum<?> uploadOperation) {
454        UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
455        uriBuilder = uriBuilder.replaceQueryParam(OperationParam.NAME, uploadOperation).
456          queryParam(DataParam.NAME, Boolean.TRUE);
457        return uriBuilder.build(null);
458      }
459    
460    
461      /**
462       * Binding to handle PUT requests.
463       *
464       * @param is the inputstream for the request payload.
465       * @param user the principal of the user making the request.
466       * @param uriInfo the of the request.
467       * @param path the path for operation.
468       * @param op the HttpFS operation of the request.
469       * @param params the HttpFS parameters of the request.
470       *
471       * @return the request response.
472       *
473       * @throws IOException thrown if an IO error occurred. Thrown exceptions are
474       * handled by {@link HttpFSExceptionProvider}.
475       * @throws FileSystemAccessException thrown if a FileSystemAccess releated
476       * error occurred. Thrown exceptions are handled by
477       * {@link HttpFSExceptionProvider}.
478       */
479      @PUT
480      @Path("{path:.*}")
481      @Consumes({"*/*"})
482      @Produces({MediaType.APPLICATION_JSON})
483      public Response put(InputStream is,
484                           @Context Principal user,
485                           @Context UriInfo uriInfo,
486                           @PathParam("path") String path,
487                           @QueryParam(OperationParam.NAME) OperationParam op,
488                           @Context Parameters params)
489        throws IOException, FileSystemAccessException {
490        Response response;
491        path = makeAbsolute(path);
492        MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name());
493        String doAs = params.get(DoAsParam.NAME, DoAsParam.class);
494        switch (op.value()) {
495          case CREATE: {
496            Boolean hasData = params.get(DataParam.NAME, DataParam.class);
497            if (!hasData) {
498              response = Response.temporaryRedirect(
499                createUploadRedirectionURL(uriInfo,
500                  HttpFSFileSystem.Operation.CREATE)).build();
501            } else {
502              Short permission = params.get(PermissionParam.NAME,
503                                             PermissionParam.class);
504              Boolean override = params.get(OverwriteParam.NAME,
505                                            OverwriteParam.class);
506              Short replication = params.get(ReplicationParam.NAME,
507                                             ReplicationParam.class);
508              Long blockSize = params.get(BlockSizeParam.NAME,
509                                          BlockSizeParam.class);
510              FSOperations.FSCreate command =
511                new FSOperations.FSCreate(is, path, permission, override,
512                                          replication, blockSize);
513              fsExecute(user, doAs, command);
514              AUDIT_LOG.info(
515                "[{}] permission [{}] override [{}] replication [{}] blockSize [{}]",
516                new Object[]{path, permission, override, replication, blockSize});
517              response = Response.status(Response.Status.CREATED).build();
518            }
519            break;
520          }
521          case MKDIRS: {
522            Short permission = params.get(PermissionParam.NAME,
523                                           PermissionParam.class);
524            FSOperations.FSMkdirs command =
525              new FSOperations.FSMkdirs(path, permission);
526            JSONObject json = fsExecute(user, doAs, command);
527            AUDIT_LOG.info("[{}] permission [{}]", path, permission);
528            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
529            break;
530          }
531          case RENAME: {
532            String toPath = params.get(DestinationParam.NAME, DestinationParam.class);
533            FSOperations.FSRename command =
534              new FSOperations.FSRename(path, toPath);
535            JSONObject json = fsExecute(user, doAs, command);
536            AUDIT_LOG.info("[{}] to [{}]", path, toPath);
537            response = Response.ok(json).type(MediaType.APPLICATION_JSON).build();
538            break;
539          }
540          case SETOWNER: {
541            String owner = params.get(OwnerParam.NAME, OwnerParam.class);
542            String group = params.get(GroupParam.NAME, GroupParam.class);
543            FSOperations.FSSetOwner command =
544              new FSOperations.FSSetOwner(path, owner, group);
545            fsExecute(user, doAs, command);
546            AUDIT_LOG.info("[{}] to (O/G)[{}]", path, owner + ":" + group);
547            response = Response.ok().build();
548            break;
549          }
550          case SETPERMISSION: {
551            Short permission = params.get(PermissionParam.NAME,
552                                          PermissionParam.class);
553            FSOperations.FSSetPermission command =
554              new FSOperations.FSSetPermission(path, permission);
555            fsExecute(user, doAs, command);
556            AUDIT_LOG.info("[{}] to [{}]", path, permission);
557            response = Response.ok().build();
558            break;
559          }
560          case SETREPLICATION: {
561            Short replication = params.get(ReplicationParam.NAME,
562                                           ReplicationParam.class);
563            FSOperations.FSSetReplication command =
564              new FSOperations.FSSetReplication(path, replication);
565            JSONObject json = fsExecute(user, doAs, command);
566            AUDIT_LOG.info("[{}] to [{}]", path, replication);
567            response = Response.ok(json).build();
568            break;
569          }
570          case SETTIMES: {
571            Long modifiedTime = params.get(ModifiedTimeParam.NAME,
572                                           ModifiedTimeParam.class);
573            Long accessTime = params.get(AccessTimeParam.NAME,
574                                         AccessTimeParam.class);
575            FSOperations.FSSetTimes command =
576              new FSOperations.FSSetTimes(path, modifiedTime, accessTime);
577            fsExecute(user, doAs, command);
578            AUDIT_LOG.info("[{}] to (M/A)[{}]", path,
579                           modifiedTime + ":" + accessTime);
580            response = Response.ok().build();
581            break;
582          }
583          default: {
584            throw new IOException(
585              MessageFormat.format("Invalid HTTP PUT operation [{0}]",
586                                   op.value()));
587          }
588        }
589        return response;
590      }
591    
592    }