This is an automated email from the ASF dual-hosted git repository.

weichiu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new a92e10741a HDDS-8278. O3fs/ofs to support setTimes() API (#4720)
a92e10741a is described below

commit a92e10741a5b3e4a2603f1fd436353994511f5b1
Author: Wei-Chiu Chuang <[email protected]>
AuthorDate: Wed May 31 15:35:21 2023 -0700

    HDDS-8278. O3fs/ofs to support setTimes() API (#4720)
---
 .../apache/hadoop/ozone/client/OzoneBucket.java    |  14 ++
 .../ozone/client/protocol/ClientProtocol.java      |  13 ++
 .../apache/hadoop/ozone/client/rpc/RpcClient.java  |  10 +
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |   1 +
 .../org/apache/hadoop/ozone/audit/OMAction.java    |   3 +-
 .../ozone/om/protocol/OzoneManagerProtocol.java    |  12 +
 ...OzoneManagerProtocolClientSideTranslatorPB.java |  22 ++
 .../hadoop/fs/ozone/TestOzoneFileSystem.java       |  24 ++
 .../hadoop/fs/ozone/TestRootedOzoneFileSystem.java |  27 +++
 .../src/main/proto/OmClientProtocol.proto          |  12 +
 .../java/org/apache/hadoop/ozone/om/OMMetrics.java |   5 +
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |   5 +
 .../om/ratis/utils/OzoneManagerRatisUtils.java     |   5 +
 .../BucketLayoutAwareOMKeyRequestFactory.java      |  11 +
 .../ozone/om/request/key/OMKeySetTimesRequest.java | 244 +++++++++++++++++++++
 .../request/key/OMKeySetTimesRequestWithFSO.java   | 162 ++++++++++++++
 .../om/response/key/OMKeySetTimesResponse.java     |  78 +++++++
 .../response/key/OMKeySetTimesResponseWithFSO.java |  84 +++++++
 .../request/TestBucketLayoutAwareOMKeyFactory.java |   6 +-
 .../om/request/key/TestOMSetTimesRequest.java      | 114 ++++++++++
 .../request/key/TestOMSetTimesRequestWithFSO.java  | 106 +++++++++
 .../fs/ozone/BasicOzoneClientAdapterImpl.java      |   6 +
 .../hadoop/fs/ozone/BasicOzoneFileSystem.java      |  10 +
 .../ozone/BasicRootedOzoneClientAdapterImpl.java   |   8 +
 .../fs/ozone/BasicRootedOzoneFileSystem.java       |  14 ++
 .../apache/hadoop/fs/ozone/OzoneClientAdapter.java |   2 +
 .../java/org/apache/hadoop/fs/ozone/Statistic.java |   4 +-
 .../hadoop/ozone/client/ClientProtocolStub.java    |   5 +
 28 files changed, 1002 insertions(+), 5 deletions(-)

diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
index 57c612f224..300ae817c4 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
@@ -890,6 +890,20 @@ public class OzoneBucket extends WithMetadata {
     return result;
   }
 
+  /**
+   * Builder for OmBucketInfo.
+   /**
+   * Set time to a key in this bucket.
+   * @param keyName Full path name to the key in the bucket.
+   * @param mtime Modification time. Unchanged if -1.
+   * @param atime Access time. Unchanged if -1.
+   * @throws IOException
+   */
+  public void setTimes(String keyName, long mtime, long atime)
+      throws IOException {
+    proxy.setTimes(ozoneObj, keyName, mtime, atime);
+  }
+
   public void setSourcePathExist(boolean b) {
     this.sourcePathExist = b;
   }
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index 1647d11e31..ceb3fcad1c 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -1071,4 +1071,17 @@ public interface ClientProtocol {
                                     String token, int pageSize,
                                     boolean forceFullDiff)
       throws IOException;
+
+  /**
+   * Time to be set for given Ozone object. This operations updates 
modification
+   * time and access time for the given key.
+   * @param obj Ozone object.
+   * @param keyName Full path name to the key in the bucket.
+   * @param mtime Modification time. Unchanged if -1.
+   * @param atime Access time. Unchanged if -1.
+   *
+   * @throws IOException if there is error.
+   * */
+  void setTimes(OzoneObj obj, String keyName, long mtime, long atime)
+      throws IOException;
 }
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index 06a1d3d61d..81d10381a9 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -2341,6 +2341,16 @@ public class RpcClient implements ClientProtocol {
     return ozoneManagerClient.setBucketOwner(builder.build());
   }
 
+  @Override
+  public void setTimes(OzoneObj obj, String keyName, long mtime, long atime)
+      throws IOException {
+    OmKeyArgs.Builder builder = new OmKeyArgs.Builder()
+        .setVolumeName(obj.getVolumeName())
+        .setBucketName(obj.getBucketName())
+        .setKeyName(keyName);
+    ozoneManagerClient.setTimes(builder.build(), mtime, atime);
+  }
+
   public ExecutorService getECReconstructExecutor() {
     // local ref to a volatile to ensure access
     // to a completed initialized object
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 9d510aa739..f366ae1875 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -315,6 +315,7 @@ public final class OmUtils {
     case SnapshotMoveDeletedKeys:
     case SnapshotPurge:
     case RecoverLease:
+    case SetTimes:
       return false;
     default:
       LOG.error("CmdType {} is not categorized as readOnly or not.", cmdType);
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
index c7ee247569..3b96267c97 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
@@ -98,7 +98,8 @@ public enum OMAction implements AuditAction {
   CREATE_SNAPSHOT,
   LIST_SNAPSHOT,
   DELETE_SNAPSHOT,
-  SNAPSHOT_MOVE_DELETED_KEYS;
+  SNAPSHOT_MOVE_DELETED_KEYS,
+  SET_TIMES;
 
   @Override
   public String getAction() {
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
index 713953b634..1a9f3c6a0b 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
@@ -998,4 +998,16 @@ public interface OzoneManagerProtocol
    */
   boolean recoverLease(String volumeName, String bucketName,
                               String keyName) throws IOException;
+
+  /**
+   * Update modification time and access time of a file.
+   * Access time is currently ignored by Ozone Manager.
+   *
+   * @param keyArgs - The key argument.
+   * @param mtime - modification time.
+   * @param atime - access time. Ignored by Ozone Manager.
+   * @throws IOException
+   */
+  void setTimes(OmKeyArgs keyArgs, long mtime, long atime)
+      throws IOException;
 }
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
index 5501bf1a14..39206e5094 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
@@ -171,6 +171,7 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetAclR
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetBucketPropertyRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetS3SecretRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetS3SecretResponse;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetTimesRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetVolumePropertyRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantAssignAdminRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantAssignUserAccessIdRequest;
@@ -2210,6 +2211,27 @@ public final class 
OzoneManagerProtocolClientSideTranslatorPB
     return recoverLeaseResponse.getResponse();
   }
 
+  @Override
+  public void setTimes(OmKeyArgs args, long mtime, long atime)
+      throws IOException {
+    KeyArgs keyArgs = KeyArgs.newBuilder()
+        .setVolumeName(args.getVolumeName())
+        .setBucketName(args.getBucketName())
+        .setKeyName(args.getKeyName())
+        .build();
+    SetTimesRequest setTimesRequest =
+        SetTimesRequest.newBuilder()
+            .setKeyArgs(keyArgs)
+            .setMtime(mtime)
+            .setAtime(atime)
+            .build();
+
+    OMRequest omRequest = createOMRequest(Type.SetTimes)
+        .setSetTimesRequest(setTimesRequest).build();
+
+    handleError(submitRequest(omRequest));
+  }
+
   @VisibleForTesting
   public OmTransport getTransport() {
     return transport;
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystem.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystem.java
index 754470960b..3ae28b9121 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystem.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystem.java
@@ -1752,4 +1752,28 @@ public class TestOzoneFileSystem {
     assertHasPathCapabilities(fs, root, FS_ACLS);
     assertHasPathCapabilities(fs, root, FS_CHECKSUMS);
   }
+
+  @Test
+  public void testSetTimes() throws Exception {
+    // Create a file
+    String testKeyName = "testKey1";
+    Path path = new Path(OZONE_URI_DELIMITER, testKeyName);
+    try (FSDataOutputStream stream = fs.create(path)) {
+      stream.write(1);
+    }
+
+    long mtime = 1000;
+    fs.setTimes(path, mtime, 2000);
+
+    FileStatus fileStatus = fs.getFileStatus(path);
+    // verify that mtime is updated as expected.
+    Assert.assertEquals(mtime, fileStatus.getModificationTime());
+
+    long mtimeDontUpdate = -1;
+    fs.setTimes(path, mtimeDontUpdate, 2000);
+
+    fileStatus = fs.getFileStatus(path);
+    // verify that mtime is NOT updated as expected.
+    Assert.assertEquals(mtime, fileStatus.getModificationTime());
+  }
 }
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
index 25f3dff3c1..4ea79970f2 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
@@ -2516,4 +2516,31 @@ public class TestRootedOzoneFileSystem {
         () -> ofs.getSnapshotDiffReport(volumePath1, finalFromSnap,
             finalToSnap));
   }
+
+  @Test
+  public void testSetTimes() throws Exception {
+    // Create a file
+    OzoneBucket bucket1 =
+        TestDataUtil.createVolumeAndBucket(client, bucketLayout);
+    Path volumePath1 = new Path(OZONE_URI_DELIMITER, bucket1.getVolumeName());
+    Path bucketPath1 = new Path(volumePath1, bucket1.getName());
+    Path path = new Path(bucketPath1, "key1");
+    try (FSDataOutputStream stream = fs.create(path)) {
+      stream.write(1);
+    }
+
+    long mtime = 1000;
+    fs.setTimes(path, mtime, 2000);
+
+    FileStatus fileStatus = fs.getFileStatus(path);
+    // verify that mtime is updated as expected.
+    Assert.assertEquals(mtime, fileStatus.getModificationTime());
+
+    long mtimeDontUpdate = -1;
+    fs.setTimes(path, mtimeDontUpdate, 2000);
+
+    fileStatus = fs.getFileStatus(path);
+    // verify that mtime is NOT updated as expected.
+    Assert.assertEquals(mtime, fileStatus.getModificationTime());
+  }
 }
diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 3596cb2a40..869713bf3c 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -134,6 +134,7 @@ enum Type {
   TransferLeadership = 117;
   SnapshotPurge = 118;
   RecoverLease = 119;
+  SetTimes = 120;
 }
 
 message OMRequest {
@@ -252,6 +253,7 @@ message OMRequest {
   optional SnapshotPurgeRequest             SnapshotPurgeRequest           = 
118;
 
   optional RecoverLeaseRequest              RecoverLeaseRequest            = 
119;
+  optional SetTimesRequest                   SetTimesRequest               = 
120;
 }
 
 message OMResponse {
@@ -362,6 +364,7 @@ message OMResponse {
   optional hdds.TransferLeadershipResponseProto   TransferOmLeadershipResponse 
 = 117;
   optional SnapshotPurgeResponse              SnapshotPurgeResponse         = 
118;
   optional RecoverLeaseResponse              RecoverLeaseResponse          = 
119;
+  optional SetTimesResponse                   SetTimesResponse             = 
120;
 }
 
 enum Status {
@@ -1878,6 +1881,15 @@ message RecoverLeaseResponse {
   optional bool response = 1;
 }
 
+message SetTimesRequest {
+  required KeyArgs keyArgs = 1;
+  required uint64 mtime    = 2;
+  required uint64 atime    = 3;
+}
+
+message SetTimesResponse {
+}
+
 /**
  The OM service that takes care of Ozone namespace.
 */
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java
index 5151e0dfc7..4a15a43ffb 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java
@@ -89,6 +89,7 @@ public class OMMetrics implements OmMetadataReaderMetrics {
   private @Metric MutableCounterLong numSetAcl;
   private @Metric MutableCounterLong numGetAcl;
   private @Metric MutableCounterLong numRemoveAcl;
+  private @Metric MutableCounterLong numSetTime;
   private @Metric MutableCounterLong numGetKeyInfo;
 
   // Failure Metrics
@@ -832,6 +833,10 @@ public class OMMetrics implements OmMetadataReaderMetrics {
     numRemoveAcl.incr();
   }
 
+  public void incNumSetTime() {
+    numSetTime.incr();
+  }
+
   @Override
   public void incNumGetKeyInfo() {
     numGetKeyInfo.incr();
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 45982556ce..9e57c4db8a 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -4499,6 +4499,11 @@ public final class OzoneManager extends 
ServiceRuntimeInfoImpl
     return false;
   }
 
+  @Override
+  public void setTimes(OmKeyArgs keyArgs, long mtime, long atime)
+      throws IOException {
+  }
+
   /**
    * Write down Layout version of a finalized feature to DB on finalization.
    * @param lvm OMLayoutVersionManager
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
index b092e05850..595cf80b4d 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
@@ -314,6 +314,11 @@ public final class OzoneManagerRatisUtils {
       volumeName = keyArgs.getVolumeName();
       bucketName = keyArgs.getBucketName();
       break;
+    case SetTimes:
+      keyArgs = omRequest.getSetTimesRequest().getKeyArgs();
+      volumeName = keyArgs.getVolumeName();
+      bucketName = keyArgs.getBucketName();
+      break;
     default:
       throw new IllegalStateException("Unrecognized write command " +
           "type request" + cmdType);
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/BucketLayoutAwareOMKeyRequestFactory.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/BucketLayoutAwareOMKeyRequestFactory.java
index 051e6e71e9..4321b7872c 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/BucketLayoutAwareOMKeyRequestFactory.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/BucketLayoutAwareOMKeyRequestFactory.java
@@ -36,6 +36,8 @@ import 
org.apache.hadoop.ozone.om.request.key.OMAllocateBlockRequest;
 import org.apache.hadoop.ozone.om.request.key.OMAllocateBlockRequestWithFSO;
 import org.apache.hadoop.ozone.om.request.key.OMKeyCommitRequest;
 import org.apache.hadoop.ozone.om.request.key.OMKeyCommitRequestWithFSO;
+import org.apache.hadoop.ozone.om.request.key.OMKeySetTimesRequest;
+import org.apache.hadoop.ozone.om.request.key.OMKeySetTimesRequestWithFSO;
 import org.apache.hadoop.ozone.om.request.key.OMKeysDeleteRequest;
 import org.apache.hadoop.ozone.om.request.key.OMKeysRenameRequest;
 import org.apache.hadoop.ozone.om.request.key.OmKeysDeleteRequestWithFSO;
@@ -180,6 +182,15 @@ public final class BucketLayoutAwareOMKeyRequestFactory {
     addRequestClass(Type.CompleteMultiPartUpload,
         S3MultipartUploadCompleteRequestWithFSO.class,
         BucketLayout.FILE_SYSTEM_OPTIMIZED);
+
+    // SetTimes
+    addRequestClass(Type.SetTimes,
+        OMKeySetTimesRequest.class,
+        BucketLayout.OBJECT_STORE
+    );
+    addRequestClass(Type.SetTimes,
+        OMKeySetTimesRequestWithFSO.class,
+        BucketLayout.FILE_SYSTEM_OPTIMIZED);
   }
 
   private BucketLayoutAwareOMKeyRequestFactory() {
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequest.java
new file mode 100644
index 0000000000..6dce387d1b
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequest.java
@@ -0,0 +1,244 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.request.key;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
+import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.audit.OMAction;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
+import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
+import org.apache.hadoop.ozone.om.response.key.OMKeySetTimesResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetTimesRequest;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetTimesResponse;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+
+import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
+
+/**
+ * Handle add SetTimes request for key.
+ */
+public class OMKeySetTimesRequest extends OMKeyRequest {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(OMKeySetTimesRequest.class);
+
+  @Override
+  public OMRequest preExecute(OzoneManager ozoneManager) throws IOException {
+    OMRequest request = super.preExecute(ozoneManager);
+    SetTimesRequest setTimesRequest = request.getSetTimesRequest();
+    String keyPath = setTimesRequest.getKeyArgs().getKeyName();
+    String normalizedKeyPath =
+        validateAndNormalizeKey(ozoneManager.getEnableFileSystemPaths(),
+            keyPath, getBucketLayout());
+
+    OzoneManagerProtocolProtos.KeyArgs keyArgs =
+        OzoneManagerProtocolProtos.KeyArgs.newBuilder()
+            .setVolumeName(getVolumeName())
+            .setBucketName(getBucketName())
+            .setKeyName(normalizedKeyPath)
+            .build();
+
+    return request.toBuilder()
+        .setSetTimesRequest(
+            setTimesRequest.toBuilder()
+                .setKeyArgs(keyArgs)
+                .setMtime(getModificationTime()))
+        .build();
+  }
+
+  private final String volumeName;
+  private final String bucketName;
+  private final String keyName;
+  private final long modificationTime;
+
+  public OMKeySetTimesRequest(OMRequest omRequest, BucketLayout bucketLayout) {
+    super(omRequest, bucketLayout);
+    OzoneManagerProtocolProtos.SetTimesRequest setTimesRequest =
+        getOmRequest().getSetTimesRequest();
+    volumeName = setTimesRequest.getKeyArgs().getVolumeName();
+    bucketName = setTimesRequest.getKeyArgs().getBucketName();
+    keyName = setTimesRequest.getKeyArgs().getKeyName();
+    // ignore accessTime
+    modificationTime = setTimesRequest.getMtime();
+  }
+
+  protected String getVolumeName() {
+    return volumeName;
+  }
+
+  protected String getBucketName() {
+    return bucketName;
+  }
+
+  protected String getKeyName() {
+    return keyName;
+  }
+
+  protected long getModificationTime() {
+    return modificationTime;
+  }
+
+  protected OMResponse.Builder onInit() {
+    return OmResponseUtil.getOMResponseBuilder(getOmRequest());
+  }
+
+  private OMClientResponse onSuccess(OMResponse.Builder omResponse,
+      OmKeyInfo omKeyInfo, boolean operationResult) {
+    omResponse.setSuccess(operationResult);
+    omResponse.setSetTimesResponse(SetTimesResponse.newBuilder());
+    return new OMKeySetTimesResponse(omResponse.build(), omKeyInfo);
+  }
+
+  /**
+   * Get the om client response on failure case with lock.
+   * @param omResponse
+   * @param exception
+   * @return OMClientResponse
+   */
+  protected OMClientResponse onFailure(OMResponse.Builder omResponse,
+      IOException exception) {
+    return new OMKeySetTimesResponse(createErrorOMResponse(
+        omResponse, exception), getBucketLayout());
+  }
+
+  protected void onComplete(Result result, IOException exception,
+      AuditLogger auditLogger, Map<String, String> auditMap) {
+    switch (result) {
+    case SUCCESS:
+      LOG.debug("Set mtime: {} to path: {} success!", modificationTime,
+          getKeyName());
+      break;
+    case FAILURE:
+      LOG.warn("Set mtime {} to path {} failed!", modificationTime,
+          getKeyName(), exception);
+      break;
+    default:
+      LOG.error("Unrecognized Result for OMKeySetTimesRequest: {}",
+          getOmRequest());
+    }
+
+    auditMap.put(OzoneConsts.VOLUME, getVolumeName());
+    auditMap.put(OzoneConsts.BUCKET, getBucketName());
+    auditMap.put(OzoneConsts.KEY, getKeyName());
+    auditMap.put(OzoneConsts.MODIFICATION_TIME,
+        String.valueOf(getModificationTime()));
+    auditLog(auditLogger, buildAuditMessage(OMAction.SET_TIMES, auditMap,
+        exception, getOmRequest().getUserInfo()));
+  }
+
+  protected void apply(OmKeyInfo omKeyInfo) {
+    // No need to check not null here, this will never be called with null.
+    long mtime = getModificationTime();
+    if (mtime >= 0) {
+      omKeyInfo.setModificationTime(getModificationTime());
+    }
+  }
+
+  @Override
+  public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
+      long trxnLogIndex, OzoneManagerDoubleBufferHelper omDoubleBufferHelper) {
+    ozoneManager.getMetrics().incNumSetTime();
+    OmKeyInfo omKeyInfo;
+
+    OMResponse.Builder omResponse = onInit();
+    OMClientResponse omClientResponse = null;
+    IOException exception = null;
+
+    OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+    boolean lockAcquired = false;
+    String volume = null;
+    String bucket = null;
+    String key;
+    boolean operationResult = false;
+    Result result;
+    try {
+      if (getModificationTime() < -1) {
+        throw new OMException(OMException.ResultCodes.INVALID_REQUEST);
+      }
+      volume = getVolumeName();
+      bucket = getBucketName();
+      key = getKeyName();
+
+      // check Acl
+      if (ozoneManager.getAclsEnabled()) {
+        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
+            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE_ACL,
+            volume, bucket, key);
+      }
+      lockAcquired = omMetadataManager.getLock().acquireWriteLock(
+          BUCKET_LOCK, volume, bucket);
+
+      String dbKey = omMetadataManager.getOzoneKey(volume, bucket, key);
+      omKeyInfo = omMetadataManager.getKeyTable(getBucketLayout())
+          .get(dbKey);
+
+      if (omKeyInfo == null) {
+        throw new OMException(OMException.ResultCodes.KEY_NOT_FOUND);
+      }
+
+      operationResult = true;
+      apply(omKeyInfo);
+      omKeyInfo.setUpdateID(trxnLogIndex, ozoneManager.isRatisEnabled());
+
+      // update cache.
+      omMetadataManager.getKeyTable(getBucketLayout())
+          .addCacheEntry(new CacheKey<>(dbKey),
+              CacheValue.get(trxnLogIndex, omKeyInfo));
+
+      omClientResponse = onSuccess(omResponse, omKeyInfo, operationResult);
+      result = Result.SUCCESS;
+    } catch (IOException ex) {
+      result = Result.FAILURE;
+      exception = ex;
+      omClientResponse = onFailure(omResponse, ex);
+    } finally {
+      addResponseToDoubleBuffer(trxnLogIndex, omClientResponse,
+          omDoubleBufferHelper);
+      if (lockAcquired) {
+        omMetadataManager.getLock().releaseWriteLock(BUCKET_LOCK, volume,
+            bucket);
+      }
+    }
+
+    Map<String, String> auditMap = new LinkedHashMap<>();
+    onComplete(result, exception, ozoneManager.getAuditLogger(), auditMap);
+
+    return omClientResponse;
+  }
+}
+
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequestWithFSO.java
new file mode 100644
index 0000000000..fa9f7a20e3
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequestWithFSO.java
@@ -0,0 +1,162 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.om.request.key;
+
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
+import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
+import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
+import org.apache.hadoop.ozone.om.request.file.OMFileRequest;
+import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
+import org.apache.hadoop.ozone.om.response.key.OMKeySetTimesResponseWithFSO;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.KEY_NOT_FOUND;
+import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
+
+/**
+ * Handle set times request for bucket for prefix layout.
+ */
+public class OMKeySetTimesRequestWithFSO extends OMKeySetTimesRequest {
+
+  @Override
+  public OzoneManagerProtocolProtos.OMRequest preExecute(
+      OzoneManager ozoneManager) throws IOException {
+    return super.preExecute(ozoneManager);
+  }
+
+  public OMKeySetTimesRequestWithFSO(
+      OzoneManagerProtocolProtos.OMRequest omReq, BucketLayout bucketLayout) {
+    super(omReq, bucketLayout);
+  }
+
+  @Override
+  public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
+      long trxnLogIndex, OzoneManagerDoubleBufferHelper omDoubleBufferHelper) {
+    OmKeyInfo omKeyInfo = null;
+
+    OzoneManagerProtocolProtos.OMResponse.Builder omResponse = onInit();
+    OMClientResponse omClientResponse = null;
+    IOException exception = null;
+
+    OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+    boolean lockAcquired = false;
+    String volume = null;
+    String bucket = null;
+    String key = null;
+    boolean operationResult = false;
+    Result result = null;
+    try {
+      volume = getVolumeName();
+      bucket = getBucketName();
+      key = getKeyName();
+
+      // check Acl
+      if (ozoneManager.getAclsEnabled()) {
+        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
+            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE_ACL,
+            volume, bucket, key);
+      }
+      lockAcquired = omMetadataManager.getLock()
+          .acquireWriteLock(BUCKET_LOCK, volume, bucket);
+      OzoneFileStatus keyStatus = OMFileRequest
+          .getOMKeyInfoIfExists(omMetadataManager, volume, bucket, key, 0);
+      if (keyStatus == null) {
+        throw new OMException("Key not found. Key:" + key, KEY_NOT_FOUND);
+      }
+      omKeyInfo = keyStatus.getKeyInfo();
+      final long volumeId = omMetadataManager.getVolumeId(volume);
+      final long bucketId = omMetadataManager.getBucketId(volume, bucket);
+      final String dbKey = omMetadataManager.getOzonePathKey(volumeId, 
bucketId,
+          omKeyInfo.getParentObjectID(), omKeyInfo.getFileName());
+      boolean isDirectory = keyStatus.isDirectory();
+      operationResult = true;
+      apply(omKeyInfo);
+      omKeyInfo.setUpdateID(trxnLogIndex, ozoneManager.isRatisEnabled());
+
+      // update cache.
+      if (isDirectory) {
+        Table<String, OmDirectoryInfo> dirTable =
+            omMetadataManager.getDirectoryTable();
+        dirTable.addCacheEntry(new CacheKey<>(dbKey),
+            CacheValue.get(trxnLogIndex,
+                OMFileRequest.getDirectoryInfo(omKeyInfo)));
+      } else {
+        omMetadataManager.getKeyTable(getBucketLayout())
+            .addCacheEntry(new CacheKey<>(dbKey),
+                CacheValue.get(trxnLogIndex, omKeyInfo));
+      }
+      omClientResponse = onSuccess(omResponse, omKeyInfo, operationResult,
+          isDirectory, volumeId, bucketId);
+      result = Result.SUCCESS;
+    } catch (IOException ex) {
+      result = Result.FAILURE;
+      exception = ex;
+      omClientResponse = onFailure(omResponse, ex);
+    } finally {
+      addResponseToDoubleBuffer(trxnLogIndex, omClientResponse,
+          omDoubleBufferHelper);
+      if (lockAcquired) {
+        omMetadataManager.getLock()
+            .releaseWriteLock(BUCKET_LOCK, volume, bucket);
+      }
+    }
+
+    Map<String, String> auditMap = new LinkedHashMap<>();
+    onComplete(result, exception, ozoneManager.getAuditLogger(), auditMap);
+
+    return omClientResponse;
+  }
+
+  @Override
+  protected OzoneManagerProtocolProtos.OMResponse.Builder onInit() {
+    return OmResponseUtil.getOMResponseBuilder(getOmRequest());
+  }
+
+  private OMClientResponse onSuccess(OMResponse.Builder omResponse,
+      OmKeyInfo omKeyInfo, boolean operationResult, boolean isDir,
+      long volumeId, long bucketId) {
+    omResponse.setSuccess(operationResult);
+    omResponse.setSetTimesResponse(
+        OzoneManagerProtocolProtos.SetTimesResponse.newBuilder());
+    return new OMKeySetTimesResponseWithFSO(omResponse.build(), omKeyInfo,
+        isDir, getBucketLayout(), volumeId, bucketId);
+  }
+
+  @Override
+  protected OMClientResponse onFailure(OMResponse.Builder omResponse,
+      IOException exception) {
+    return new OMKeySetTimesResponseWithFSO(createErrorOMResponse(
+        omResponse, exception), getBucketLayout());
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeySetTimesResponse.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeySetTimesResponse.java
new file mode 100644
index 0000000000..895bdfdf91
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeySetTimesResponse.java
@@ -0,0 +1,78 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.response.key;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.response.CleanupTableInfo;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+
+import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.KEY_TABLE;
+
+/**
+ * Response for Bucket acl request.
+ */
+@CleanupTableInfo(cleanupTables = KEY_TABLE)
+public class OMKeySetTimesResponse extends OmKeyResponse {
+
+  private OmKeyInfo omKeyInfo;
+
+  public OMKeySetTimesResponse(@Nonnull OMResponse omResponse,
+      @Nonnull OmKeyInfo omKeyInfo) {
+    super(omResponse);
+    this.omKeyInfo = omKeyInfo;
+  }
+
+  public OMKeySetTimesResponse(@Nonnull OMResponse omResponse,
+      @Nonnull OmKeyInfo omKeyInfo,
+      @Nonnull BucketLayout bucketLayout) {
+    super(omResponse, bucketLayout);
+    this.omKeyInfo = omKeyInfo;
+  }
+
+  /**
+   * For when the request is not successful.
+   * For a successful request, the other constructor should be used.
+   */
+  public OMKeySetTimesResponse(@Nonnull OMResponse omResponse,
+      @Nonnull BucketLayout bucketLayout) {
+    super(omResponse, bucketLayout);
+  }
+
+  @Override
+  public void addToDBBatch(OMMetadataManager omMetadataManager,
+      BatchOperation batchOperation) throws IOException {
+
+    String dbKey = omMetadataManager.getOzoneKey(omKeyInfo.getVolumeName(),
+        omKeyInfo.getBucketName(), omKeyInfo.getKeyName());
+    omMetadataManager.getKeyTable(getBucketLayout())
+        .putWithBatch(batchOperation, dbKey, omKeyInfo);
+  }
+
+  public OmKeyInfo getOmKeyInfo() {
+    return omKeyInfo;
+  }
+}
+
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeySetTimesResponseWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeySetTimesResponseWithFSO.java
new file mode 100644
index 0000000000..024e9eb6ca
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeySetTimesResponseWithFSO.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.om.response.key;
+
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.request.file.OMFileRequest;
+import org.apache.hadoop.ozone.om.response.CleanupTableInfo;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.DIRECTORY_TABLE;
+import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.FILE_TABLE;
+
+/**
+ * Response for Bucket acl request for prefix layout.
+ */
+@CleanupTableInfo(cleanupTables = { FILE_TABLE, DIRECTORY_TABLE })
+public class OMKeySetTimesResponseWithFSO extends OMKeySetTimesResponse {
+
+  private boolean isDirectory;
+  private long volumeId;
+  private long bucketId;
+
+  public OMKeySetTimesResponseWithFSO(
+      @NotNull OzoneManagerProtocolProtos.OMResponse omResponse,
+      @NotNull OmKeyInfo omKeyInfo, boolean isDirectory,
+      @Nonnull BucketLayout bucketLayout, @Nonnull long volumeId,
+      @Nonnull long bucketId) {
+    super(omResponse, omKeyInfo, bucketLayout);
+    this.isDirectory = isDirectory;
+    this.volumeId = volumeId;
+    this.bucketId = bucketId;
+  }
+
+  /**
+   * For when the request is not successful.
+   * For a successful request, the other constructor should be used.
+   *
+   * @param omResponse
+   */
+  public OMKeySetTimesResponseWithFSO(
+      @NotNull OzoneManagerProtocolProtos.OMResponse omResponse,
+      BucketLayout bucketLayout) {
+    super(omResponse, bucketLayout);
+  }
+
+  @Override
+  public void addToDBBatch(OMMetadataManager omMetadataManager,
+      BatchOperation batchOperation) throws IOException {
+
+    String ozoneDbKey = omMetadataManager.getOzonePathKey(volumeId, bucketId,
+        getOmKeyInfo().getParentObjectID(), getOmKeyInfo().getFileName());
+    if (isDirectory) {
+      OmDirectoryInfo dirInfo = OMFileRequest.getDirectoryInfo(getOmKeyInfo());
+      omMetadataManager.getDirectoryTable()
+          .putWithBatch(batchOperation, ozoneDbKey, dirInfo);
+    } else {
+      omMetadataManager.getKeyTable(getBucketLayout())
+          .putWithBatch(batchOperation, ozoneDbKey, getOmKeyInfo());
+    }
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestBucketLayoutAwareOMKeyFactory.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestBucketLayoutAwareOMKeyFactory.java
index 18491a98db..7003799fbe 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestBucketLayoutAwareOMKeyFactory.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestBucketLayoutAwareOMKeyFactory.java
@@ -109,9 +109,9 @@ public class TestBucketLayoutAwareOMKeyFactory {
           LOG.info("Validated request class instantiation for cmdType " + k);
         });
 
-    Assert.assertEquals(12, omKeyReqsFSO.size());
-    Assert.assertEquals(13, omKeyReqsLegacy.size());
-    Assert.assertEquals(13, omKeyReqsOBS.size());
+    Assert.assertEquals(13, omKeyReqsFSO.size());
+    Assert.assertEquals(14, omKeyReqsLegacy.size());
+    Assert.assertEquals(14, omKeyReqsOBS.size());
     // Check if the number of instantiated OMKeyRequest classes is equal to
     // the number of keys in the mapping.
     Assert.assertEquals(
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMSetTimesRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMSetTimesRequest.java
new file mode 100644
index 0000000000..4f7dd5cd04
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMSetTimesRequest.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.om.request.key;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetTimesRequest;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+
+/**
+ * Test cases for OMSetTimesRequest.
+ */
+public class TestOMSetTimesRequest extends TestOMKeyRequest {
+
+  /**
+   * Verify that setTimes() on key works as expected.
+   * @throws Exception
+   */
+  @Test
+  public void testKeySetTimesRequest() throws Exception {
+    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager, getBucketLayout());
+    String ozoneKey = addKeyToTable();
+
+    long mtime = 2000;
+    executeAndReturn(mtime);
+    // Verify result of setting times.
+    long keyMtime =
+        omMetadataManager.getKeyTable(getBucketLayout()).get(ozoneKey)
+            .getModificationTime();
+    Assert.assertEquals(mtime, keyMtime);
+
+    long newMtime = -1;
+    executeAndReturn(newMtime);
+    keyMtime =
+        omMetadataManager.getKeyTable(getBucketLayout()).get(ozoneKey)
+            .getModificationTime();
+    Assert.assertEquals(mtime, keyMtime);
+  }
+
+  protected void executeAndReturn(long mtime)
+      throws IOException {
+    long atime = 1000;
+    OMRequest setTimesRequest = createSetTimesKeyRequest(mtime, atime);
+    OMKeySetTimesRequest omKeySetTimesRequest =
+        getOmKeySetTimesRequest(setTimesRequest);
+    OMRequest preExecuteRequest = 
omKeySetTimesRequest.preExecute(ozoneManager);
+    omKeySetTimesRequest = getOmKeySetTimesRequest(preExecuteRequest);
+
+    OMClientResponse omClientResponse = omKeySetTimesRequest
+        .validateAndUpdateCache(ozoneManager, 100L,
+            ozoneManagerDoubleBufferHelper);
+    OMResponse omSetTimesResponse = omClientResponse.getOMResponse();
+    Assert.assertNotNull(omSetTimesResponse.getSetTimesResponse());
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omSetTimesResponse.getStatus());
+  }
+
+  private OMRequest createSetTimesKeyRequest(long mtime, long atime) {
+    OzoneManagerProtocolProtos.KeyArgs keyArgs =
+        OzoneManagerProtocolProtos.KeyArgs.newBuilder()
+            .setVolumeName(volumeName)
+            .setBucketName(bucketName)
+            .setKeyName(keyName)
+            .build();
+    SetTimesRequest setTimesRequest = SetTimesRequest.newBuilder()
+        .setKeyArgs(keyArgs)
+        .setMtime(mtime)
+        .setAtime(atime)
+        .build();
+
+    return OMRequest.newBuilder().setClientId(UUID.randomUUID().toString())
+        .setCmdType(OzoneManagerProtocolProtos.Type.SetTimes)
+        .setSetTimesRequest(setTimesRequest)
+        .build();
+  }
+
+  protected String addKeyToTable() throws Exception {
+    OMRequestTestUtils.addKeyToTable(false, false, volumeName, bucketName,
+        keyName, clientID, replicationType, replicationFactor, 1L,
+        omMetadataManager);
+
+    return omMetadataManager.getOzoneKey(volumeName, bucketName,
+        keyName);
+  }
+
+  protected OMKeySetTimesRequest getOmKeySetTimesRequest(
+      OMRequest setTimesRequest) {
+    return new OMKeySetTimesRequest(setTimesRequest, getBucketLayout());
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMSetTimesRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMSetTimesRequestWithFSO.java
new file mode 100644
index 0000000000..93d48cb9e9
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMSetTimesRequestWithFSO.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.om.request.key;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
+import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.request.file.OMFileRequest;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import org.apache.hadoop.util.Time;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test cases for TestOMSetTimesRequestWithFSO.
+ */
+public class TestOMSetTimesRequestWithFSO extends TestOMSetTimesRequest {
+
+  private static final String PARENT_DIR = "c/d/e";
+  private static final String FILE_NAME = "file1";
+
+  /**
+   * Verify that setTimes() on directory works as expected.
+   * @throws Exception
+   */
+  @Test
+  public void testDirSetTimesRequest() throws Exception {
+    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager, getBucketLayout());
+    addKeyToTable();
+    keyName = PARENT_DIR;
+
+    long mtime = 2000;
+    executeAndReturn(mtime);
+    OzoneFileStatus keyStatus = OMFileRequest.getOMKeyInfoIfExists(
+        omMetadataManager, volumeName, bucketName, keyName, 0);
+    assertNotNull(keyStatus);
+    assertTrue(keyStatus.isDirectory());
+    long keyMtime = keyStatus.getKeyInfo().getModificationTime();
+    Assert.assertEquals(mtime, keyMtime);
+
+    long newMtime = -1;
+    executeAndReturn(newMtime);
+    keyStatus = OMFileRequest.getOMKeyInfoIfExists(
+        omMetadataManager, volumeName, bucketName, keyName, 0);
+    assertNotNull(keyStatus);
+    assertTrue(keyStatus.isDirectory());
+    keyMtime = keyStatus.getKeyInfo().getModificationTime();
+    Assert.assertEquals(mtime, keyMtime);
+  }
+
+  protected String addKeyToTable() throws Exception {
+    String key = PARENT_DIR + "/" + FILE_NAME;
+    keyName = key; // updated key name
+
+    // Create parent dirs for the path
+    long parentId = OMRequestTestUtils
+        .addParentsToDirTable(volumeName, bucketName, PARENT_DIR,
+            omMetadataManager);
+
+    OmKeyInfo omKeyInfo = OMRequestTestUtils
+        .createOmKeyInfo(volumeName, bucketName, key,
+            HddsProtos.ReplicationType.RATIS, HddsProtos.ReplicationFactor.ONE,
+            parentId + 1, parentId, 100, Time.now());
+    OMRequestTestUtils
+        .addFileToKeyTable(false, false, FILE_NAME, omKeyInfo, -1, 50,
+            omMetadataManager);
+    final long volumeId = omMetadataManager.getVolumeId(
+        omKeyInfo.getVolumeName());
+    final long bucketId = omMetadataManager.getBucketId(
+        omKeyInfo.getVolumeName(), omKeyInfo.getBucketName());
+    return omMetadataManager.getOzonePathKey(
+        volumeId, bucketId, omKeyInfo.getParentObjectID(), FILE_NAME);
+  }
+
+  protected OMKeySetTimesRequest getOmKeySetTimesRequest(
+      OMRequest setTimesRequest) {
+    return new OMKeySetTimesRequestWithFSO(setTimesRequest, getBucketLayout());
+  }
+
+  @Override
+  public BucketLayout getBucketLayout() {
+    return BucketLayout.FILE_SYSTEM_OPTIMIZED;
+  }
+}
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java
index 669b1ea18b..4905fd1d30 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java
@@ -706,4 +706,10 @@ public class BasicOzoneClientAdapterImpl implements 
OzoneClientAdapter {
     }
     return snapshotDiffResponse.getSnapshotDiffReport();
   }
+
+  @Override
+  public void setTimes(String key, long mtime, long atime) throws IOException {
+    incrementCounter(Statistic.INVOCATION_SET_TIMES, 1);
+    bucket.setTimes(key, mtime, atime);
+  }
 }
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
index 3dd8276394..573ffcd461 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
@@ -938,6 +938,16 @@ public class BasicOzoneFileSystem extends FileSystem {
         OM_SNAPSHOT_INDICATOR + OZONE_URI_DELIMITER + snapshot);
   }
 
+  @Override
+  public void setTimes(Path f, long mtime, long atime) throws IOException {
+    incrementCounter(Statistic.INVOCATION_SET_TIMES, 1);
+    statistics.incrementWriteOps(1);
+    LOG.trace("setTimes() path:{}", f);
+    Path qualifiedPath = makeQualified(f);
+    String key = pathToKey(qualifiedPath);
+    adapter.setTimes(key, mtime, atime);
+  }
+
   /**
    * A private class implementation for iterating list of file status.
    *
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
index 8902dd43dc..d0e6ab2c77 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
@@ -1364,4 +1364,12 @@ public class BasicRootedOzoneClientAdapterImpl
     return ozoneClient.getProxy().getOzoneManagerClient().recoverLease(
             volume.getName(), bucket.getName(), ofsPath.getKeyName());
   }
+
+  public void setTimes(String key, long mtime, long atime) throws IOException {
+    incrementCounter(Statistic.INVOCATION_SET_TIMES, 1);
+    OFSPath ofsPath = new OFSPath(key, config);
+
+    OzoneBucket bucket = getBucket(ofsPath, false);
+    bucket.setTimes(ofsPath.getKeyName(), mtime, atime);
+  }
 }
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
index ca8a0b0d68..4934bb341c 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
@@ -1549,4 +1549,18 @@ public class BasicRootedOzoneFileSystem extends 
FileSystem {
     return adapterImpl.recoverLease(f);
   }
 
+  @Override
+  public void setTimes(Path f, long mtime, long atime) throws IOException {
+    incrementCounter(Statistic.INVOCATION_SET_TIMES, 1);
+    statistics.incrementWriteOps(1);
+    LOG.trace("setTimes() path:{}", f);
+    Path qualifiedPath = makeQualified(f);
+    String key = pathToKey(qualifiedPath);
+    // Handle DistCp /NONE path
+    if (key.equals("NONE")) {
+      throw new FileNotFoundException("File not found. path /NONE.");
+    }
+    adapter.setTimes(key, mtime, atime);
+  }
+
 }
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientAdapter.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientAdapter.java
index 50fe9a71c1..afc8fd39e2 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientAdapter.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientAdapter.java
@@ -91,4 +91,6 @@ public interface OzoneClientAdapter {
   SnapshotDiffReport getSnapshotDiffReport(Path snapshotDir,
       String fromSnapshot, String toSnapshot)
       throws IOException, InterruptedException;
+
+  void setTimes(String key, long mtime, long atime) throws IOException;
 }
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/Statistic.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/Statistic.java
index 136d999859..09b9cdd777 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/Statistic.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/Statistic.java
@@ -72,7 +72,9 @@ public enum Statistic {
   INVOCATION_OPEN(CommonStatisticNames.OP_OPEN,
       "Calls of open()"),
   INVOCATION_RENAME(CommonStatisticNames.OP_RENAME,
-      "Calls of rename()");
+      "Calls of rename()"),
+  INVOCATION_SET_TIMES(CommonStatisticNames.OP_SET_TIMES,
+      "Calls of setTimes()");
 
   private static final Map<String, Statistic> SYMBOL_MAP =
       new HashMap<>(Statistic.values().length);
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
index ff464604f8..69f8a2f499 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
@@ -642,4 +642,9 @@ public class ClientProtocolStub implements ClientProtocol {
     return null;
   }
 
+  @Override
+  public void setTimes(OzoneObj obj, String keyName, long mtime, long atime)
+      throws IOException {
+  }
+
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to