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 }